As your question does not specify which variant of C++ to use, this solution uses C++17. This means it can avoid the recursion.
We do this via binary folds to resolve the composition of functions. To do this, we first need to convert function objects to objects that can be composed via a binary operation:
template<class F>
struct compose_t {
F f;
template<class Lhs, class Rhs>
auto operator*( compose_t<Lhs> lhs, compose_t<Rhs> rhs ) {
auto r =
[lhs = std::move(lhs).f, rhs = std::move(rhs).f](auto&&...args)
->decltype(auto)
{ return lhs(rhs(decltype(args)(args)...)); }
return compose_t<decltype(r)>{ std::move(r) };
}
template<class...Args>
decltype(auto) operator()(Args&&...args){
return f(std::forward<Args>(args)...);
}
};
template<class F>
compose_t<F> compose(F f) { return {std::forward<F>(f)}; }
this creates a composable function object that composes with *
.
Next, we need an object that represents "construct a T on the heap", without specifying how it is done by the object:
template<class T>
auto maker() {
return [](auto&&...args) {
return std::make_unique<T>( decltype(args)(args)...) )
};
}
maker
returns a function object that represents callilng make_unique<T>
on a set of arguments to be provided later. I could do this with raw pointers, but I refuse.
template<typename ...Args>
std::unique_ptr<Sender> createSenderChain() {
return (compose( maker<Args>() ) * ...)();
}
and done. Note that I use unique_ptr<Sender>
s instead of Sender*
s, because I refuse to provide crap code you shouldn't use.