在下面的代码中,为什么std::function<void (X)>允许绑定到函数void f(X&&)?
#include <functional>
struct X {};
void f1(X x) {}
void f2(X& x) {}
void f3(const X&) {}
void f4(X&& x) {}
int main()
{
X x;
f1(x); // ok
f2(x); // ok
f3(x); // ok
// f4(x); // doesn't compile
std::function<void (X)> ff1(f1); // ok
//std::function<void (X)> ff2(f2); // doesn't compile
std::function<void (X)> ff3(f3); // ok
std::function<void (X)> ff4(f4); // ok... why?
return 0;
}
Run Code Online (Sandbox Code Playgroud)
为什么允许这样做背后的直觉是std::function什么,它将如何由 实现,以及当您将左值参数传递给std::function,然后调用接受右值引用的函数时,这实际上意味着什么?
您可能知道,该std::function类型是任何类似函数的类型的多态包装器。这包括函数指针、带有operator()lambda 的类。
由于它必须实现类型擦除,因此它仅检查您发送的内容是否可以使用它接收的参数进行调用。类型擦除将简单地进行隐式转换,就像任何函数调用一样。
如果您查看 的 cppreference 页面std::function::function,您会注意到此要求:
5) 用 初始化目标
std::move(f)。如果f是指向函数的空指针或指向成员的空指针,*this则调用后将为空。此构造不参与重载解析,除非f是可调用的参数类型Args...和返回值类型R。
实际上, type 的表达式X可以完全绑定到 type 的右值引用X&&。尝试这个:
X&& x = X{};
// likewise, both prvalue and xvalue works:
f4(X{});
f4(std::move(x));
Run Code Online (Sandbox Code Playgroud)
在此示例中,X{}是 类型 的纯右值X。右值引用可以绑定到这样的临时对象。
在 内部std::function,每个参数都被转发。转发就像听起来一样:它转发参数来调用被包装的函数,就像你直接调用它一样。但是转发不会保留纯右值,而是将参数作为 xvalue 转发。xvalues 和 prvalues 都是右值的特例。
在std::functionand 转发的上下文中,将参数传递给被包装的函数对Xand使用相同的表达式X&&。没有缺点就不能转发纯价值。
要了解有关转发的更多信息,请参阅使用 std::forward 的主要目的是什么以及它解决了哪些问题?
隐式转换可以更进一步:从 adouble到 an int。事实上,如果你将一个浮点类型发送给一个接受 的函数int,遗憾的是,C++ 将执行一个隐式转换。该std::function实施也不能幸免于这种效果。考虑这个例子:
#include <functional>
#include <cstdio>
int main() {
auto const f = std::function<void(double)>{
[](int a) {
std::printf("%d", a);
}
};
f(9.8); // prints 9
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这是可能的,因为它std::function是函数对象的包装器。函数指针需要是确切的类型。
此外,f2无法编译,因为对 lvalue ( X&) 的可变引用无法绑定临时值。所以X& x = X{}不会工作,X& x2 = std::move(x1)也不会工作。
另外,f4(x)不要编译,因为x它不是临时的。表达式(x)实际上是X&. X&&只绑定到临时的,例如X{}or std::move(x)。要将变量转换为临时变量,您需要使用std::move,它执行转换。请参阅为什么我必须对右值引用调用移动?更多细节。
| 归档时间: |
|
| 查看次数: |
155 次 |
| 最近记录: |