1

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 .

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 2
    Parameters can't be `auto` (only in lambdas they can). You can't create templates this way. You have to use `template ` syntax and use template parameters as types of function parameters. (Though your code might work with a compiler extension, so that may not be a problem.) – HolyBlackCat Apr 24 '18 at 22:09
  • 3
    You may also have problems because C++ overloads `round` with multiple parameter types. – Zan Lynx Apr 24 '18 at 22:10
  • In order to match input of f and ouput of g ? it works well for a simple auto timeFuncInvocation(auto&& f, auto&&... param) { time_point start = system_clock::now(), end; forward(f)(forward(param)...); end = system_clock::now; return duration_cast(end-start).count(); } what's the difference ? –  Apr 24 '18 at 22:16
  • let's use fabs of @ZanLynx, still failing :) –  Apr 24 '18 at 22:18
  • I may be missing something here, but even if you define such a thing, why would it be easier to use `fComposition(f, g, x)` instead of just using `f(g(x))`? – gflegar Apr 24 '18 at 22:19
  • Well @GoranFlegar, my context is invoking timeFuncInvocation on a MonteCarlo that is performing round(distr(gen)) in each case of the loop. –  Apr 24 '18 at 22:27
  • @HolyBlackCat I just tried to replace those auto from signature by typename, and it ends on the same issue –  Apr 24 '18 at 22:35
  • 1
    Changing `math.h` to `cmath` and fixing the minor typos in `fComposition` worked for me. See on [godbolt](https://godbolt.org/g/VRxnhh). – md5i Apr 25 '18 at 00:35

2 Answers2

5

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.

bipll
  • 11,747
  • 1
  • 18
  • 32
0

The include of random includes cmath, which in libstdc++ also defines several of the math operators (including round) in the default namespace as well as in the std namespace. (See this answer for the rationale.) And C++'s round has multiple overloads. As a result, you have several versions of round available, and your function doesn't know which round you want to use, thus the error message about ambiguity. The correct solution is to disambiguate which round you mean. You can do this with a static cast:

static_cast<double(*)(double)>(round)

Since you have to go through the trouble anyway, you may as well also use the cmath header instead of math.h and use std::round instead. At least then you know that it's going to be overloaded up front.

md5i
  • 3,018
  • 1
  • 18
  • 32