hlebyshek
hlebyshek

Reputation: 5

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)

Upvotes: 0

Views: 215

Answers (2)

n. m. could be an AI
n. m. could be an AI

Reputation: 119847

You need a couple of utility classes.

  #include <utility>
  #include <tuple>
  #include <functional>

  template <typename Func>
  struct wrap
  {
      wrap (Func func) : func{func} {}
      template <typename ... T>
      auto operator()(std::tuple<T...>&& args) {
          return std::apply(func, std::forward<std::tuple<T...>>(args));
      }
      Func func;
  };

  template <typename Func>
  struct unwrap
  {
      unwrap (Func func) : func{func} {}
      template <typename ... T>
      auto operator()(T&& ... args) {
          return func(std::tuple(std::forward<T>(args)...));
      }
      Func func;
  };

With this:

  template <typename Func1, typename Func2>
  auto compose (Func1 func1, Func2 func2)
  {
      using namespace std::placeholders;
      return unwrap(std::bind(func1, std::bind(wrap(func2), _1)));
  }

And a test:

  int foo(int a, int b) { return a+b; }
  int bar(int a) { return a*2; }

  int main()
  {
      std::cout << compose(bar, foo)(3,4);
  }

Conceptification and forwardification are left as an exercise for the reader.

But you should really forget about std::bind and use lambdas.

Upvotes: 1

Caleth
Caleth

Reputation: 62531

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));
}

Upvotes: 0

Related Questions