Composition of functions from the standard

Viewed 88

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 ?

2 Answers

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

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.

Related