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));
}