0

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)

7
  • 4
    Why would you abandon lambda and choose std::bind, which is outdated and not recommended? Commented Nov 7, 2023 at 14:42
  • 1
    Where did you hear that std::bind is outdated? In my opinion, there are better(or simple, readable, laconic) ways for code composition than lambda. Commented Nov 7, 2023 at 15:09
  • 3
    std::bind is not "simple, readable, laconic" compared to a lambda Commented Nov 7, 2023 at 16:27
  • All depends on how you use it) Commented Nov 7, 2023 at 16:38
  • There are numerous recommendations to avoid std::bind(). For example, in this CppCon video, at 25:08 .. 33:05 (already from 2015) about <functional>, Stephan T. Lavavej (the implementor of Visual Studio's C++ Standard Library) wraps it up by saying, "Avoid using bind()!" And as I understand, std::bind() is essentially an obsolete construct that was invented only because there were no lambdas yet in the C++ language. Commented Nov 7, 2023 at 22:00

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for answer. Extra code in my situation will be superfluous. I will put up with using lambda)
0

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

2 Comments

that's why I wrote std::bind*, meaning std::bind, std::bind_front, std::bind_back
@hlebyshek the answer is still no, because you need to wait for the result of inner to have the argument to outer

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.