我可以使用 std::bind* 编写函数吗?

Can I compose functions using std::bind*?

提问人:hlebyshek 提问时间:11/7/2023 最后编辑:hlebyshek 更新时间:11/8/2023 访问量:114

问:

我正在玩 C++20 中的函数式编程并编写类似的东西:

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

这是工作,但我想通过使用 std::bind* 而不是 lambda 来重构两个参数的 compose。 所以,我认为这个:

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

显然是(特别是如果使用没有重载运算符的函数):

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

也许你有一个想法,我该怎么做,或者我不能这样做的原因?

问题是如何检索内部函数的参数。

std::bind(outer, std::bind_front(inner));我通过类比一个参数来尝试可变模板参数(以及数百万个其他变体),但它不起作用。std::bind(outer, std::bind(inner, _1));

P.S. 对不起我的英语)

C++ 式编程 C++20 函数组合 stdbind

评论

4赞 康桓瑋 11/7/2023
你为什么要放弃 lambda 而选择 ,这已经过时了,不推荐?std::bind
1赞 hlebyshek 11/7/2023
你从哪里听说 std::bind 已经过时了?在我看来,有比 lambda 更好(或简单、可读、简洁)的代码组合方法。
3赞 Caleth 11/8/2023
std::bind与 lambda 相比,它不是“简单、可读、简洁”
0赞 hlebyshek 11/8/2023
一切都取决于你如何使用它)
0赞 heap underrun 11/8/2023
有许多建议要避免。例如,在这个 CppCon 视频中,在 25:08 .. 33:05(已经从 2015 年开始)关于 ,Stephan T. Lavavej(Visual Studio 的 C++ 标准库的实现者)总结说:“避免使用 !”据我了解,它本质上是一个过时的结构,只是因为 C++ 语言中还没有 lambda 而发明的。std::bind()<functional>bind()std::bind()

答:

0赞 Caleth 11/8/2023 #1

不能将模板替换为使用 的实现,因为需要为调用提供可变数量的占位符。你能做的最好的事情就是支持内部调用的特定参数,直到(实现定义的)数量限制。composestd::bindbindstd::placeholders

也不能使用 ,因为函数对象通常不是它在调用时返回的值。std::bind_front

因此,您必须有一个中间函数对象,其中包含内部和外部函数,最简单的语法是 lambda。如果你真的想,你可以把它包装起来,但没有意义。std::bind_front

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

评论

0赞 hlebyshek 11/8/2023
这就是为什么我写了 , 意思是 , ,std::bind*std::bindstd::bind_frontstd::bind_back
0赞 Caleth 11/8/2023
@hlebyshek答案仍然是否定的,因为您需要等待结果才能进行论证innerouter
1赞 n. m. could be an AI 11/8/2023 #2

您需要几个实用程序类。

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

有了这个:

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

还有一个测试:

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

概念化和前瞻性留给读者作为练习。

但是你真的应该忘记并使用lambdas。std::bind

评论

0赞 hlebyshek 11/9/2023
谢谢你的回答。在我的情况下,额外的代码将是多余的。我会忍受使用 lambda)