模板函数无法识别自动变量引用的 lambda
Template function cannot recognize lambda referred by an auto variable
在 c++17 中,我有一个模板函数,它接受某种 lambda 作为输入。但是它只识别那些具有显式类型的和那些使用 auto 的被拒绝。
为什么会这样,有什么方法可以将自动变量和模板函数结合起来,以指定形式的 lambda 作为输入?
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
class C {};
vector<C> vec(2);
// ForEach func requires lambda must take Object with type C
// only, while its return type can vary as the function is templated.
template<typename T>
void ForEach(function<T (C a)>& lambda) {
std::for_each(begin(vec), end(vec), lambda);
};
int main()
{
auto
f_add_display4 = [](C i) {
};
std::function<void(C)>
f_add_display3 = f_add_display4;
ForEach(f_add_display3);
// ForEach(f_add_display4); // This line won't compile
}
std 函数不是 lambda,lambda 也不是 std 函数。
您的函数采用标准函数。因此,当传递给它时,它会将模板参数推导到 std 函数。如果你传递一个 lambda,它不能推导出任何东西。
其次,std函数是类型擦除类型,不是接口。您的函数试图推导类型擦除 class 的模板参数。这样做是一种反模式。
template<class F>
void ForEach(F lambda) {
std::for_each(begin(vec), end(vec), lambda);
}
这有效。
如果你想重构 F
以接受一些签名,在 c++20 中你可以这样做:
void ForEach(std::invocable<C> auto lambda) {
std::for_each(begin(vec), end(vec), lambda);
}
或 c++17:
template<std::invocable<C> F>
void ForEach(F lambda) {
std::for_each(begin(vec), end(vec), lambda);
}
这里其实有两个基本问题导致推导失败:
首先,对于任何签名 Signature
,lambda 表达式的类型永远不会是 std::function<Signature>
。但是,您的函数需要一个非 const
引用参数。由于类型不同,需要进行转换,这将是一个临时对象,而临时对象永远不会绑定到非 const
引用。您可以通过使用 const
引用作为参数来解决此问题:
template <typename T>
void ForEach(function<T(C)> const& lambda) { ... }
另一个问题是 ForEach
采用了一组概念上开放的潜在论点。但是,您拥有的参数不是直接匹配:无法根据 lambda 类型推断 T
类型以与函数参数完全匹配。相反,需要进行转换。编译器不会试图找到哪个实例可以作为实例化的目标,尽管在这种情况下只有一个选择。如果您指定目标类型(并做出先前选择使参数 const&
:
ForEach<void>(f_add_display4);
我建议不要 约束函数以function<T(C)>
开始!与使用实际的 lambda 函数相比,这很可能是一种悲观:虽然 lambda 函数是静态类型的,并且通常可以很好地优化,但 std::function<Signaure>
却并非如此。虽然后者有时可以优化,但通常不是。如果你想限制函数只接受 C
类型的参数,你可以用其他一些方法来做到这一点,可能涉及 C++17 编译器的 SFINAE 或 C++20+ 的概念。
也就是说,我建议使用
template <typename Fun>
void ForEach(Fun&& lambda) {
...
}
...的,如果你想使用C++20的概念来约束函数
template <typename Fun>
requires requires (Fun lambda, C c) { lambda(c); }
void ForEach(Fun&& lambda) {
...
}
f_add_display3
的类型不是 std::function<void(C)>
。它是某种未命名的 class 类型(某些 class 带有 void operator()(C i)
)。
您可以像在此处一样将 lamda 转换为 std::function<void(C)>
:
std::function<void(C)> f_add_display3 = f_add_display4;
不过,模板参数推导不考虑隐式转换。
如果将参数更改为 const
引用,则可以显式指定模板参数:
template<typename T>
void ForEach(const function<T (C a)>& lambda) {
// ...
};
ForEach<void>(f_add_display4); // This line will compile !
或者您完全放弃对 std::function
的不必要转换:
template <typename F>
void ForEach(F f) {
std::for_each(begin(vec), end(vec),f);
};
在 c++17 中,我有一个模板函数,它接受某种 lambda 作为输入。但是它只识别那些具有显式类型的和那些使用 auto 的被拒绝。
为什么会这样,有什么方法可以将自动变量和模板函数结合起来,以指定形式的 lambda 作为输入?
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
class C {};
vector<C> vec(2);
// ForEach func requires lambda must take Object with type C
// only, while its return type can vary as the function is templated.
template<typename T>
void ForEach(function<T (C a)>& lambda) {
std::for_each(begin(vec), end(vec), lambda);
};
int main()
{
auto
f_add_display4 = [](C i) {
};
std::function<void(C)>
f_add_display3 = f_add_display4;
ForEach(f_add_display3);
// ForEach(f_add_display4); // This line won't compile
}
std 函数不是 lambda,lambda 也不是 std 函数。
您的函数采用标准函数。因此,当传递给它时,它会将模板参数推导到 std 函数。如果你传递一个 lambda,它不能推导出任何东西。
其次,std函数是类型擦除类型,不是接口。您的函数试图推导类型擦除 class 的模板参数。这样做是一种反模式。
template<class F>
void ForEach(F lambda) {
std::for_each(begin(vec), end(vec), lambda);
}
这有效。
如果你想重构 F
以接受一些签名,在 c++20 中你可以这样做:
void ForEach(std::invocable<C> auto lambda) {
std::for_each(begin(vec), end(vec), lambda);
}
或 c++17:
template<std::invocable<C> F>
void ForEach(F lambda) {
std::for_each(begin(vec), end(vec), lambda);
}
这里其实有两个基本问题导致推导失败:
首先,对于任何签名
Signature
,lambda 表达式的类型永远不会是std::function<Signature>
。但是,您的函数需要一个非const
引用参数。由于类型不同,需要进行转换,这将是一个临时对象,而临时对象永远不会绑定到非const
引用。您可以通过使用const
引用作为参数来解决此问题:template <typename T> void ForEach(function<T(C)> const& lambda) { ... }
另一个问题是
ForEach
采用了一组概念上开放的潜在论点。但是,您拥有的参数不是直接匹配:无法根据 lambda 类型推断T
类型以与函数参数完全匹配。相反,需要进行转换。编译器不会试图找到哪个实例可以作为实例化的目标,尽管在这种情况下只有一个选择。如果您指定目标类型(并做出先前选择使参数const&
:ForEach<void>(f_add_display4);
我建议不要 约束函数以function<T(C)>
开始!与使用实际的 lambda 函数相比,这很可能是一种悲观:虽然 lambda 函数是静态类型的,并且通常可以很好地优化,但 std::function<Signaure>
却并非如此。虽然后者有时可以优化,但通常不是。如果你想限制函数只接受 C
类型的参数,你可以用其他一些方法来做到这一点,可能涉及 C++17 编译器的 SFINAE 或 C++20+ 的概念。
也就是说,我建议使用
template <typename Fun>
void ForEach(Fun&& lambda) {
...
}
...的,如果你想使用C++20的概念来约束函数
template <typename Fun>
requires requires (Fun lambda, C c) { lambda(c); }
void ForEach(Fun&& lambda) {
...
}
f_add_display3
的类型不是 std::function<void(C)>
。它是某种未命名的 class 类型(某些 class 带有 void operator()(C i)
)。
您可以像在此处一样将 lamda 转换为 std::function<void(C)>
:
std::function<void(C)> f_add_display3 = f_add_display4;
不过,模板参数推导不考虑隐式转换。
如果将参数更改为 const
引用,则可以显式指定模板参数:
template<typename T>
void ForEach(const function<T (C a)>& lambda) {
// ...
};
ForEach<void>(f_add_display4); // This line will compile !
或者您完全放弃对 std::function
的不必要转换:
template <typename F>
void ForEach(F f) {
std::for_each(begin(vec), end(vec),f);
};