c++functional-programmingc++20function-compositionstdbind

Can I compose functions using std::bind*?


I'm playing with functional programming in C++20 and write something like that:

template <class OuterFn, class InnerFn, class... Args>
concept Composable =
    std::invocable<OuterFn, std::invoke_result_t<InnerFn, Args...>>;

template <class OuterFn, class InnerFn>
constexpr auto
compose(OuterFn&& outer, InnerFn&& inner)
{
    return [
        out = std::forward<OuterFn>(outer),
        in = std::forward<InnerFn>(inner)
    ]<class... Args>(Args && ... args)
        requires Composable<OuterFn, InnerFn, Args...>
    {
        using std::invoke, std::forward;
        return invoke(out, invoke(in, forward<Args>(args)...));
    };
}

template <class OuterFn, class InnerFn>
constexpr auto
operator*(OuterFn&& outer, InnerFn&& inner)
{
    using std::forward;
    return compose(forward<OuterFn>(outer), forward<InnerFn>(inner));
}

template <class... RestFn>
constexpr auto
compose(RestFn&&... rest)
{
    return (std::forward<RestFn>(rest) * ...);
}

This is work, but I want to refactor compose for two arguments by using std::bind* instead of lambda. So, I think that this:

using eq_t = std::equal_to<const std::string_view>;
constexpr auto eq_42 = std::bind_front(eq_t{}, "42");

is clearly than(especially if using function without overloaded operator for it):

constexpr auto eq_42 = [](const std::string_view sv){ return sv == "42"; };

Maybe You have an ideas how can I do this, or reasons why I can't do this?

The problem is how to retrieve arguments to inner function.

std::bind(outer, std::bind_front(inner)); I tried this for variadic template arguments(and million other variants) by analogy from std::bind(outer, std::bind(inner, _1)); for one argument, but it doesn't work.

P.S. Sorry for my english)


Solution

  • You can't replace your template compose with an implementation using std::bind, because you would need to supply a variable number of placeholders to the bind call. The best you can do is support a specific arity of arguments to the inner call, up to the (implementation-defined) limit of how many std::placeholders there are.

    Nor can you use std::bind_front, because a function object in general isn't the value it returns when called.

    So you have to have an intermediate function object, holding the inner and outer functions, and the simplest syntax for that is a lambda. If you really wanted to, you could wrap that in std::bind_front, but there's no point.

    template <class OuterFn, class InnerFn>
    constexpr auto
    compose(OuterFn&& outer, InnerFn&& inner)
    {
        using std::invoke, std::forward;
        struct composer {
            template <class Out, class In, class... Args>
            auto operator()(Out&& out, In&& in, Args&&.. args) {
                return invoke(out, invoke(in, forward<Args>(args)...));
            }
        };
        // Fairly gratuitous bind_front, composer could have done the capturing of outer and inner itself
        return std::bind_front(composer{}, forward(outer), forward(inner));
    }