Is it possible to chain 3 or more multi-functors with in a pipeline functionality and an elegant syntax like pipeline?
struct Filter0{//multi-functor; will be the last one in pipeline
template<Args...args> void operator()(Args&&...args){/*not important what is done here*/}
};
template<std::invocable F> struct Filter1{//intermediate filter forwarding to a multi-functor
template<Args...args> void operator()(F&f, Args&&...args){
/*do smtg and sometime call f(std::forward<decltype(args)>(args)...);*/
}
};
template<typename Left, std::invocable Right>
auto operator|(Left left, Right right){//chains "something" with a multi-functor
return [left,right](auto&& arg){
left(right, std::forward<decltype(arg)>(arg));
};
}
Filter0 filter0;
Filter1<decltype(Filter0)> filter1;
auto pipe1 = filter1 | filter0;//OK
pipe1('x');//OK pushes 'x' down the pipeline
Filter2<decltype(Filter1)> filter2;//Filter2 would be something similar with Filter1
auto pipe2 = filter2 | (filter1 | filter0);//KO
pipe2('x');//KO
With or without parentheses in 1st KO line:
Note: I can "chain" 3 or more filters if I provide "the next one" as ctor's argument for each intermediate filter and store it as member inside the class instead having as argument in each method, something like:
Filter0 filter0;
Filter1 filter1(filter0);
Filter2 filter2(filter1);
filter2('x');//this will push 'x' down the pipeline
but in this case they are "tight" with each other from declaration. I would like them as "loose" as possible, "tight" only by type (or even not at all if possible)
You can play with a live example here: https://godbolt.org/z/6e8cfrbTo
SOLUTION 1 (simple & short): move template argument from filter class to methods (thanks RedFog & Jarod42!)
SOLUTION 2 (a bit more elaborated): make filter|filter
operation an expression template (see https://godbolt.org/z/G9z3qfsj8)
the key problem is that you demands F
, the type of the functor which filter should receive, for parameter, but you actually provide a lambda expression.
auto pipe2 = filter2 | (filter1 | filter0);
pipe2
is Filter2<Filter1<Filter0>>
, has parameter type Filter1<Filter0>
, but you provide [left,right](auto&& arg){ left(right, std::forward<decltype(arg)>(arg)); };
instead of an instance of Filter1<Filter0>
.
the most simple solution is, to let template parameter F
belong to operator()
instead of filter itself. see Demo.
Edit: I remove the all std::invocable
, it doesn't preform as it should be. I think the std::invocable
constraint in operator|
implies you want filter | value
, so that filter | (filter | (... | value)...)
, but it's impossible, the filters you design don't have enough ability to check if it receives a value instead of another filter.
Edit: |
is left-associative, maybe a right-associative design will get half the results with twice the effort. and as @Jarod42 said, unconstraint operator|
is dangerous. a better way is to make all your filters and the results of filter | filter
under the constraint of a concept (maybe named isFilter
?), so that you can filter | filter | ... filter
and even filter | filter | ... filter | value
.