patterncppModerate
C++ function composition
Viewed 0 times
functioncompositionstackoverflow
Problem
What is a good way to compose
I tried the following, and it seems to work well:
With this, one can compose functions as long as the signatures fit together. Example:
Comments and suggestions for improvement are welcome!
std::function objects in C++?I tried the following, and it seems to work well:
template
struct compose_impl
{
compose_impl(Fs&& ... fs) : functionTuple(std::forward_as_tuple(fs ...)) {}
template struct int2type{};
template
auto apply(int2type, Ts&& ... ts)
{
return std::get(functionTuple)(apply(int2type(),std::forward(ts)...));
}
static const size_t size = sizeof ... (Fs);
template
auto apply(int2type, Ts&& ... ts)
{
return std::get(functionTuple)(std::forward(ts)...);
}
template
auto operator()(Ts&& ... ts)
{
return apply(int2type(), std::forward(ts)...);
}
std::tuple functionTuple;
};
template
auto compose(Fs&& ... fs)
{
return compose_impl(std::forward(fs) ...);
}With this, one can compose functions as long as the signatures fit together. Example:
auto f1 = [](std::pair p) {return p.first + p.second; };
auto f2 = [](double x) {return std::make_pair(x, x + 1.0); };
auto f3 = [](double x, double y) {return x*y; };
auto g = compose(f1, f2, f3);
std::cout << g(2.0, 3.0) << std::endl; //prints '13', evaluated as (2*3) + ((2*3)+1)Comments and suggestions for improvement are welcome!
Solution
Generally speaking, it is really well done, for several reasons:
-
You could
-
-
You should be consistent when qualifying
As you can see, these are really minor improvements. I also have some other remarks, but those will be opinions more than actual advice:
-
-
I had some trouble understanding how your recursion worked because it was in ascending order. For some reason, I am more used to descending order. I would have overloaded
And then,
std::tuple often takes advantage of the empty base class optimization, which means that since you feed it lambdas, your class will often weigh almost nothing, and everything is correctly forwarded. The only things I see that could be improved are the following ones:-
You could
const-qualify apply and operator().-
size should be static constexpr instead of static const to make it even clearer that it is a compile-time constant.-
You should be consistent when qualifying
std::size_t: either use the prefix std:: or leave it, but stay consistent.As you can see, these are really minor improvements. I also have some other remarks, but those will be opinions more than actual advice:
-
int2type kind of already exists in the standard and is named std::integral_constant. However, I will concede that it takes another template parameter for the type and that it might be too verbose for your needs.-
I had some trouble understanding how your recursion worked because it was in ascending order. For some reason, I am more used to descending order. I would have overloaded
apply for int2type and not for int2type and performed a descending recursion. That would have allowed me to write:template
auto operator()(Ts&& ... ts)
{
return apply(int2type(), std::forward(ts)...);
}And then,
size wouldn't have had to be a member of the class anymore. But I have to admit that this is an opinion and not a guideline. Your code is good enough that I see almost nothing that could be improved :)Code Snippets
template<typename ... Ts>
auto operator()(Ts&& ... ts)
{
return apply(int2type<sizeof ... (Fs) - 1>(), std::forward<Ts>(ts)...);
}Context
StackExchange Code Review Q#63841, answer score: 12
Revisions (0)
No revisions yet.