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); }
关键修正点
- 完美转发参数:使用
std::forward保留每个绑定参数的左值/右值属性,确保右值引用参数能正确匹配。 - 移动捕获可调用对象:用
std::move转移f的所有权,避免不必要的拷贝,同时支持不可拷贝的可调用对象。 - 模板函数适配:要么显式实例化模板函数后传递给
curry,要么用lambda包装模板函数,让模板在调用时自动推导参数类型。
内容的提问来源于stack exchange,提问作者Tomasz Tarkowski
相关产品推荐
相关产品推荐

