4

I'm trying to create a function 'compose' returning the composition of callable objects given as arguments.

In mathematics, It would be the following:

F = h(g(f(x)))

Here an implementation in C++:

template <typename F>
auto compose(F&& f)
{
    return[f = std::forward<F>(f)](auto&&... args){
        return f(std::forward<decltype(args)>(args)...);
    };
}

template <typename F, typename G, typename... Functions>
auto compose(F&& f, G&& g, Functions&&... functions)
{
    return compose(
        [f = std::forward<F>(f), g = std::forward<G>(g)](auto&&... args)
        {
            return f(g(std::forward<decltype(args)>(args)...));
        },
        std::forward<Functions>(functions)...);
}

This works like a charm if I provide lambda functions as arguments:

auto f = compose(
    [](auto value) { return std::sin(value); },
    [](auto value) { return std::asin(value); }
);

However, if I use directly overloaded functions from the standard (without encapsulating them within a lambda), the compiler cannot deduce which overload to pick to instantiate the template:

auto g = compose(
    std::sin,
    std::asin
);

Here the errors using Microsoft C++ compiler:

error C2672: 'compose': no matching overloaded function found
error C2783: 'auto compose(F &&,G &&,Functions &&...)': could not deduce template argument for 'F'
message : see declaration of 'compose'
error C2783: 'auto compose(F &&,G &&,Functions &&...)': could not deduce template argument for 'G'
message : see declaration of 'compose'
error C2780: 'auto compose(F &&)': expects 1 arguments - 2 provided
message : see declaration of 'compose'

Is there a way to state the function types (of std::sin and std::asin) we want to use while creating the composed function ?

Julien Briand
  • 275
  • 1
  • 7
  • I don't think there's a good way. Some use a macro to conveniently wrap functions in macros. – HolyBlackCat Sep 13 '20 at 11:34
  • why not immediatly pass in your datatype, so that you can use it? eg. `compose(...)`. Should work like a charm then – X39 Sep 13 '20 at 15:53

2 Answers2

2

How about passing (double (&)(double)) std ::sin?

  • 1
    To maybe clarify a bit, there are [multiple overloads](https://en.cppreference.com/w/cpp/numeric/math/sin) defined for `sin` causing deduction failure. – dewaffled Sep 13 '20 at 12:01
  • While this appears to work, it's actually unspecified, and is not guaranteed to work. See https://stackoverflow.com/questions/55687044/can-i-take-the-address-of-a-function-defined-in-standard-library – cigien Sep 13 '20 at 15:43
0

When you call compose with overloaded functions, such as std::sin, the compiler can't deduce the type of the function you want.

One approach that appears to solve the problem, is forming a pointer/reference to a specific overload of the function, e.g.:

// wrong, though it might appear to work
auto g = compose(
    static_cast<double(&)(double)>(std::sin),
    static_cast<double(&)(double)>(std::asin)
);

This is not correct however, since you're not allowed to take the address of functions from std:: that are not explicitly marked as addressable. The correct way to do this is in fact, the version in your question that wraps the function call in a lambda (when constructing f).

Note that casting to a pointer/reference to disambiguate an overload will work just fine for your own overload sets, or for functions from std:: that are marked as addressable.

cigien
  • 57,834
  • 11
  • 73
  • 112