包装在模板函数中的 lamda 的类型推导
Type deduction for lamda wrapped in template function
我实现了 std::bind
版本的自定义替代方案,如下所示:
template <typename F, typename ... Ts>
constexpr auto curry(F &&f, Ts ... args) {
return [&](auto&& ... args2) {
return f(std::forward<Ts...>(args...), args2...);
};
}
此函数获取要应用的参数的函数和包,以及 returns lambda,它是 f
具有部分应用的参数 args
。
另外,为了测试这段代码,我有一个函数,它有两个参数,只有 returns 第一个:
template <typename T1, typename T2>
constexpr T1 fconst(T1 &&x, T2&&) {
return x;
}
当我像下面这样使用 curry
函数时:
int main() {
auto z2 = curry(fconst, 5);
return 0;
}
我收到编译器无法推断的错误 F
类型:
error: no matching function for call to ‘curry(<unresolved overloaded function type>, int)’
17 | auto z2 = curry(fconst, 5);
candidate: ‘template<class F, class ... Ts> constexpr auto curry(F&&, Ts ...)’
15 | constexpr auto curry(F &&f, Ts ... args) {
| ^~~~~
note: template argument deduction/substitution failed:
note: couldn’t deduce template parameter ‘F’
17 | auto z2 = curry(fconst, 5);
但是当我将 curry
函数实现为宏而不是模板函数时:
#define curry(f, args...) \
[&](auto&& ... args2) { \
return f(args, args2...); \
};
我的主要代码编译成功。
我启用了 g++11 和 c++20。
我的问题是:
- 为什么编译器无法推导模板的
F
类型,但按原样传递 lambda 时可以推导?
- 我的
curry
函数可以使用模板函数实现吗,或者宏是唯一可行的方法?
首先,您的 curry
很危险,因为里面的 lambda 通过引用捕获本地参数。一旦 curry
return 你有悬空的引用。
正确的版本是:
template <typename F, typename ... Ts>
constexpr auto curry(F &&f, Ts ... args) {
return [...args=std::move(args), f=std::forward<F>(f)](auto&& ... args2) mutable {
return std::invoke(f,args..., std::forward<decltype(args2)>(args2)...);
};
}
f
、args
处理显示了两种相同的样式。
- 在调用方复制 -
args
.
- 或copy/move(在捕获时)。
- (参数包捕获是C++20)。有一个使用元组和
std::apply
的更丑陋的解决方法,询问您是否需要它。
std::bind
从不移动它捕获的参数,它们总是作为左值传递给被调用者。这使得重复调用安全。
args2
应该正确转发。
不幸的是,没有无宏的解决方案。这是重载函数集的固有问题。不能传递这样的集合,C++ 根本不支持它。有一些论文试图解决这个问题——我知道 P1170R0。但是到目前为止 none 被接受了。
解决方法基本上就是您提出的方法以及第二个示例起作用的原因 - 宏能够粘贴函数名称,无论它是否重载(或者它实际上是什么)。
#define overload_set(overloaded_f) \
[](auto&& ... args) { \
return overloaded_f(std::forward<decltype(args)>(args)...);} \
这是就地完美转发 lambda,其中包含复制粘贴的 overloaded_f
符号。尽管如此,人们仍然不能像任何其他普通函数一样获取 this(或者更确切地说不应该)的地址,但它可以传递给例如curry
.
完整示例
#include <iostream>
template <typename F, typename... Ts>
constexpr auto curry(F&& f, Ts... args) {
return [... args = std::move(args),
f = std::forward<F>(f)](auto&&... args2) mutable {
return f(args..., std::forward<decltype(args2)>(args2)...);
};
}
template <typename T1, typename T2>
constexpr T1 fconst(T1&& x, T2&&y) {
std::cout<<"Value x:" << x<<'\n';
std::cout<<"Value y:" << y<<'\n';
return x;
}
#define overload_set(overloaded_f) \
[&](auto&&... args) { \
return overloaded_f(std::forward<decltype(args)>(args)...); \
}
struct Foo{
Foo()=default;
Foo(Foo&&)=default;
Foo(const Foo&)=delete;
operator int(){ return 42;}
};
int main() {
// Overloaded function can now be stored in a lambda.
auto stored_set= overload_set(fconst);
// And called as ordinary function.
auto ret =stored_set(1.0, 2.0);
std::cout<<"Returned value: " <<ret <<'\n';
// Overloaded set now can be passed around.
// But it is a functor, not a function.
auto curried_fnc= curry(overload_set(fconst),1.0);
// Curry works
auto ret2 = curried_fnc(2.0);
std::cout<<"Returned value: " <<ret2<<'\n';
// Move-only values work too.
std::cout<<"Curry move\n";
curried_fnc(Foo{});
}
输出
Value x:1
Value y:2
Returned value: 1
Value x:1
Value y:2
Returned value: 1
Curry move
Value x:1
Value y:42
我实现了 std::bind
版本的自定义替代方案,如下所示:
template <typename F, typename ... Ts>
constexpr auto curry(F &&f, Ts ... args) {
return [&](auto&& ... args2) {
return f(std::forward<Ts...>(args...), args2...);
};
}
此函数获取要应用的参数的函数和包,以及 returns lambda,它是 f
具有部分应用的参数 args
。
另外,为了测试这段代码,我有一个函数,它有两个参数,只有 returns 第一个:
template <typename T1, typename T2>
constexpr T1 fconst(T1 &&x, T2&&) {
return x;
}
当我像下面这样使用 curry
函数时:
int main() {
auto z2 = curry(fconst, 5);
return 0;
}
我收到编译器无法推断的错误 F
类型:
error: no matching function for call to ‘curry(<unresolved overloaded function type>, int)’
17 | auto z2 = curry(fconst, 5);
candidate: ‘template<class F, class ... Ts> constexpr auto curry(F&&, Ts ...)’
15 | constexpr auto curry(F &&f, Ts ... args) {
| ^~~~~
note: template argument deduction/substitution failed:
note: couldn’t deduce template parameter ‘F’
17 | auto z2 = curry(fconst, 5);
但是当我将 curry
函数实现为宏而不是模板函数时:
#define curry(f, args...) \
[&](auto&& ... args2) { \
return f(args, args2...); \
};
我的主要代码编译成功。
我启用了 g++11 和 c++20。
我的问题是:
- 为什么编译器无法推导模板的
F
类型,但按原样传递 lambda 时可以推导? - 我的
curry
函数可以使用模板函数实现吗,或者宏是唯一可行的方法?
首先,您的 curry
很危险,因为里面的 lambda 通过引用捕获本地参数。一旦 curry
return 你有悬空的引用。
正确的版本是:
template <typename F, typename ... Ts>
constexpr auto curry(F &&f, Ts ... args) {
return [...args=std::move(args), f=std::forward<F>(f)](auto&& ... args2) mutable {
return std::invoke(f,args..., std::forward<decltype(args2)>(args2)...);
};
}
f
、args
处理显示了两种相同的样式。- 在调用方复制 -
args
. - 或copy/move(在捕获时)。
- 在调用方复制 -
- (参数包捕获是C++20)。有一个使用元组和
std::apply
的更丑陋的解决方法,询问您是否需要它。 std::bind
从不移动它捕获的参数,它们总是作为左值传递给被调用者。这使得重复调用安全。args2
应该正确转发。
不幸的是,没有无宏的解决方案。这是重载函数集的固有问题。不能传递这样的集合,C++ 根本不支持它。有一些论文试图解决这个问题——我知道 P1170R0。但是到目前为止 none 被接受了。
解决方法基本上就是您提出的方法以及第二个示例起作用的原因 - 宏能够粘贴函数名称,无论它是否重载(或者它实际上是什么)。
#define overload_set(overloaded_f) \
[](auto&& ... args) { \
return overloaded_f(std::forward<decltype(args)>(args)...);} \
这是就地完美转发 lambda,其中包含复制粘贴的 overloaded_f
符号。尽管如此,人们仍然不能像任何其他普通函数一样获取 this(或者更确切地说不应该)的地址,但它可以传递给例如curry
.
完整示例
#include <iostream>
template <typename F, typename... Ts>
constexpr auto curry(F&& f, Ts... args) {
return [... args = std::move(args),
f = std::forward<F>(f)](auto&&... args2) mutable {
return f(args..., std::forward<decltype(args2)>(args2)...);
};
}
template <typename T1, typename T2>
constexpr T1 fconst(T1&& x, T2&&y) {
std::cout<<"Value x:" << x<<'\n';
std::cout<<"Value y:" << y<<'\n';
return x;
}
#define overload_set(overloaded_f) \
[&](auto&&... args) { \
return overloaded_f(std::forward<decltype(args)>(args)...); \
}
struct Foo{
Foo()=default;
Foo(Foo&&)=default;
Foo(const Foo&)=delete;
operator int(){ return 42;}
};
int main() {
// Overloaded function can now be stored in a lambda.
auto stored_set= overload_set(fconst);
// And called as ordinary function.
auto ret =stored_set(1.0, 2.0);
std::cout<<"Returned value: " <<ret <<'\n';
// Overloaded set now can be passed around.
// But it is a functor, not a function.
auto curried_fnc= curry(overload_set(fconst),1.0);
// Curry works
auto ret2 = curried_fnc(2.0);
std::cout<<"Returned value: " <<ret2<<'\n';
// Move-only values work too.
std::cout<<"Curry move\n";
curried_fnc(Foo{});
}
输出
Value x:1
Value y:2
Returned value: 1
Value x:1
Value y:2
Returned value: 1
Curry move
Value x:1
Value y:42