You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++20中Currying与万能引用适配问题求助

C++20柯里化代码适配右值引用/万能引用的问题解析

问题背景

以下是基于C++20实现的柯里化代码,在GCC 12.2.0和Clang 15.0.2环境下可正常编译:

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

但为函数f添加右值引用参数、为函数g添加万能引用参数后,代码无法编译:

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

constexpr auto
g(auto&&... args) { /*...*/ }

编译错误原因

1. 右值引用参数函数f的问题

原实现使用std::bind_front绑定参数时,会将传入的右值(比如2)拷贝为左值存储在绑定器对象中。后续调用时,绑定器会把这个左值传递给f的int&&参数,而左值无法绑定到右值引用,触发编译错误。

另外,原代码中lambda的[=]捕获会拷贝可调用对象,对于需要保留引用属性的场景,这种拷贝会丢失参数的右值信息。

2. 万能引用模板函数g的问题

未实例化的模板不是一个合法的可调用对象,curry(g)本质是传递了一个模板名称而非具体的函数实例,std::bind_front无法绑定未实例化的模板。即使显式实例化g<int,int,int,int>,bind_front在处理万能引用时,也会因为引用折叠规则破坏参数的原始值类别,导致后续调用无法匹配万能引用的参数要求。


修正方案

核心思路是放弃std::bind_front,改用完美转发的lambda手动实现参数绑定,保留参数的左值/右值属性;同时针对模板函数的传递做适配。

修正后的完整代码:

#include <concepts>
#include <functional>
#include <utility>

// 处理无参可调用对象的终止重载
constexpr auto curry(std::invocable auto f)
{
    return f();
}

// 处理需要至少一个参数的可调用对象
template<typename F>
constexpr auto curry(F f)
{
    // 移动捕获f,避免不必要拷贝
    return [f = std::move(f)](auto&& x) mutable {
        // 用完美转发保留x的原始值类别,绑定到新的lambda中
        return curry(
            [f = std::move(f), x = std::forward<decltype(x)>(x)]
            (auto&&... args) mutable {
                // 完美转发所有参数到原函数
                return std::invoke(f, std::forward<decltype(x)>(x), std::forward<decltype(args)>(args)...);
            }
        );
    };
}

// 右值引用参数的测试函数
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()
{
    // 传递右值给f的柯里化调用,保留右值属性
    static_assert(curry(f)(std::move(2))(std::move(3))(std::move(7)) == 42);
    // 显式实例化g的模板后传递
    static_assert(curry(g<int, int, int, int>)(1)(2)(3)(7) == 42);
    // 用lambda包装g,让模板自动推导参数类型
    static_assert(curry([](auto&&... args){ return g(std::forward<decltype(args)>(args)...); })(1)(2)(3)(7) == 42);
    static_assert(curry(h) == 42);
    static_assert(curry([](int n) { return n; })(42) == 42);
}

关键修正点

  1. 完美转发参数:使用std::forward保留每个绑定参数的左值/右值属性,确保右值引用参数能正确匹配。
  2. 移动捕获可调用对象:用std::move转移f的所有权,避免不必要的拷贝,同时支持不可拷贝的可调用对象。
  3. 模板函数适配:要么显式实例化模板函数后传递给curry,要么用lambda包装模板函数,让模板在调用时自动推导参数类型。

内容的提问来源于stack exchange,提问作者Tomasz Tarkowski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 15:45:34