Simply put, when I opt for a data-driven design, I separate the data from the behavior. Given an input as
struct Input { int value; };
I pass it to some components that operate on it. I… call some functions.
void set(Input& in, int value) { input.value = value; } void reset(Input& in) { input.value = 0; } Input in{}; set(in, 2); reset(in);
Because life is better with patterns, I’d want to have independent and configurable functions and a clear intent of their role and usage. Short story, a way to do this is a list of functions to be called with an input.
template<typename T, typename... Fs> void apply(T& in, Fs&&... fs) { (fs(in), ...); }
I’ve used the C++17 fold expression to unpack the template parameters (the list of functions).
And I can pass the input and the functions:
apply(in, set, reset);
But set
does not have the signature required by the function calls in apply
. The apply
function requires a list of functions that have only the input parameter (the data that I am operating on). Thus set
can return a lambda function with that signature:
auto set(int value) { return [value](Input& in) { in.value = value; }; } apply(in, set(2), reset);
A thing that I don’t like about the apply
function is that it can be called with no function as argument, only with the input. And it does not make any sense.
apply(in);
I want to be required to pass at least one function:
template<typename T, typename F, typename... Fs> void apply(T& in, F&& f, Fs&&... fs) { f(in); (fs(in), ...); }
Some usage examples that show how to create a business flow based on an input:
#include <cassert> #include <iostream> struct Input { int value; }; auto set(int value) { return [value](Input& in) { in.value = value; }; } auto add(int value) { return [value](Input& in) { in.value += value; }; } void triple(Input& in) { in.value *= 3; } void print(const Input& in) { std::cout << "value = " << in.value << '\n'; } class Reset { public: static void reset(Input& in) { in.value = 0; } }; auto assert_eq(int value) { return [value](Input& in) { assert(in.value == value); }; } template<typename T, typename F, typename... Fs> void apply(T& in, F&& f, Fs&&... fs) { f(in); (fs(in), ...); } int main() { Input in{}; apply(in, print, set(1), print, add(2), print, triple, print, assert_eq(9), [](const Input& in){ std::cout << "final is = " << in.value << '\n'; } ); apply(in, set(5), assert_eq(5)); apply(in, &Reset::reset, assert_eq(0)); }