C++20 和(通用)引用中的柯里化

Tom*_*ski 4 c++ currying forwarding-reference c++20

这是我使用 C++20 进行柯里化的实现:

#include <concepts>
#include <functional>

constexpr auto
curry(std::invocable auto f)
{
  return f();
}

constexpr auto
curry(auto f)
{
  return [=](auto x) { return curry(std::bind_front(f, x)); };
}

constexpr int
f(int a, int b, int c)
{
  return a * b * c;
}

constexpr auto
g(auto... args)
{
  return (1 * ... * args);
}

constexpr int
h()
{
  return 42;
}

int
main()
{
  static_assert(curry(f)(2)(3)(7) == 42);
  static_assert(curry(g<int, int, int, int>)(1)(2)(3)(7) == 42);
  static_assert(curry(h) == 42);
  static_assert(curry([](int n) { return n; })(42) == 42);
}
Run Code Online (Sandbox Code Playgroud)

此代码使用我安装的 GCC 12.2.0 和 Clang 15.0.2 进行编译。不幸的是,添加到&&任何函数后fg它就不再编译:

constexpr int
f(int&& a, int&& b, int&& c) { /*...*/ }

constexpr auto
g(auto&&... args) { /*...*/ }
Run Code Online (Sandbox Code Playgroud)

您能否解释一下错误的原因并提出可能的更正建议?

Bar*_*rry 7

问题就在这里:

constexpr auto
curry(std::invocable auto f)
{
  return f();
}
Run Code Online (Sandbox Code Playgroud)

该函数模板实际上并未受到适当的约束。这相当于:

template <typename F> requires std::invocable<F>
constexpr auto curry(F f) {
    return f();
}
Run Code Online (Sandbox Code Playgroud)

std::invocable<F>正在检查是否F可以不带参数调用...但作为右值。但主体将其作为左值调用 - 这不是您检查的内容。通常,这两种情况之间没有太大区别,因此您可以编写错误的约束。

但在这种情况下,有很大的不同。绑定器根据对象的值类别转发其底层参数。f()将绑定参数作为左值传递,std::move(f)()将绑定参数作为右值传递。由于您的函数仅接受右值,因此f()在这种情况下无效,但std::move(f)()没问题。

你需要写的是:

template <typename F> requires std::invocable<F>
constexpr auto curry(F&& f) {
    return std::forward<F>(f)();
}
Run Code Online (Sandbox Code Playgroud)

然后其他重载也需要采用转发引用以避免歧义。