BTW, is this really your code? You're not expanding params
so it should not compile.
I. The way you define composition, it is indistinguishable from a simple invocation: your fComposition(f, g, arg)
is the same as f(g(arg))
except for extra characters typing. The real composition is usually a combinator that accepts two functions and returns a closure that, when invoked on actual arguments, applies them in succession. Something like:
template<class F, class G> auto comp(F f, G g) {
return [f, g](auto &&... args) {
return f(g(std::forward<decltype(args)>(args)...));
};
}
(Note by-values bindings. In C++17, they are more advanced than twenty years ago. :) You can add std::move
s and std::forward
s by taste.)
This way you compose two functions:
auto fg = comp(f, g);
and later invoke the result on arguments:
auto x = fg(arg1, arg2);
II. But really, why limit ourselves with two operands? In Haskell, (.)
is a single binary function. In C++, we can have a whole tree of overloads:
template<class Root, class... Branches> auto comp(Root &&root, Branches &&... branches) {
return [root, branches...](auto &&...args) {
return root(branches(std::forward<decltype(args)>(args)...)...);
};
}
Now you can encapsulate any AST in a single callable:
int f(int x, int y) { return x + y; }
int g(int x) { return x * 19; }
int h(int x) { return x + 2; }
#include <iostream>
int main() {
auto fgh = comp(f, g, h);
std::cout << fgh(2) << '\n';
}
A similar technique was the only way known to me to have anonymous closures in C++ prior to 11 standard.
III. But wait, is there a library solution? In fact, yes. From std::bind
's description
If the stored argument arg is of type T for which std::is_bind_expression<T>::value == true
(for example, another bind expression was passed directly into the initial call to bind), then bind performs function composition: instead of passing the function object that the bind subexpression would return, the subexpression is invoked eagerly, and its return value is passed to the outer invokable object. If the bind subexpression has any placeholder arguments, they are shared with the outer bind (picked out of u1, u2, ...
). Specifically, the argument vn
in the std::invoke
call above is arg(std::forward<Uj>(uj)...)
and the type Vn
in the same call is std::result_of_t<T cv &(Uj&&...)>&&
(cv qualification is the same as that of g).
Sorry, no examples here at this moment. >_<
P.S. And yes, std::round
is an overloaded function so you should typecast it to specify which exactly overload you need to be composed.