Search code examples
c++c++17auto

How to define the function composition in c++17?


I would like to compute the function composition -- f ( g (param) ). Here is what I tried:

auto fComposition(auto&& f, auto&& g, auto&&... params)
{
    /* some stuff */
    auto result = std::forward<decltype(f)>(f)(
                      std::forward<decltype(g)>(g)(
                          std::forward<decltype(params)>(param)
                      )
                  );
    /* other stuff */
    return result;
};

Compiling with

g++ -std=c++17 src.cpp

basic test

#include <random>
#include <math.h>
int main()
{
    std::random_device rd;                                                                          
    std::mt19937 gen(rd());                                                                         
    std::uniform_real_distribution<double> distr(-1.0, 1.0);

    auto xx = fComposition(round, distr, gen);
    return 0;
}

I've got the message that it doesn't recognize the type of first function .


Solution

  • 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::moves and std::forwards 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.