为什么允许带有签名 void (X) 的 std::function 绑定到函数 void f(X&&)?
Why is a std::function with signature void (X) allowed to bind to a function void f(X&&)?
在下面的代码中,为什么允许 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;
}
为什么允许这样做背后的直觉是什么,std::function
将如何实现它,以及当您将左值参数传递给 std::function
时它的实际含义是什么,然后调用接受右值引用的函数?
如您所知,std::function
类型是任何类函数类型的多态包装器。这包括函数指针,类 with operator()
和 lambdas.
由于它必须实现类型擦除,它只检查您发送的内容是否可以使用它接收的参数调用。类型擦除将简单地隐式地进行转换,就像任何函数调用一样。
如果您查看 std::function::function
的 cppreference 页面,您会注意到此要求:
5) Initializes the target with std::move(f)
. If f
is a null pointer to
function or null pointer to member, *this
will be empty after the
call. This constructor does not participate in overload resolution
unless f
is Callable for argument types Args...
and return type R
.
确实,X
类型的表达式完全可以绑定到 X&&
类型的右值引用。试试这个:
X&& x = X{};
// likewise, both prvalue and xvalue works:
f4(X{});
f4(std::move(x));
在此示例中,X{}
是 X
类型的纯右值。右值引用可以绑定到此类临时文件。
在std::function
内部,每个参数都被转发。转发顾名思义:它转发参数来调用包装函数,就像你直接调用它一样。然而,转发不保持纯右值,而是将参数作为 xvalue 转发。 xvalues 和 prvalues 都是右值的特例。
在 std::function
和转发的上下文中,将参数传递给包装函数对 X
和 X&&
使用相同的表达式。纯右值性不能在没有缺点的情况下转发。
要了解有关转发的更多信息,请参阅What are the main purposes of using std::forward and which problems it solves?
隐式转换可以更进一步:从 double
到 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;
}
这是可能的,因为 std::function
是函数对象的包装器。函数指针需要是准确的类型。
此外,f2
无法编译,因为对左值 (X&
) 的可变引用无法绑定临时变量。所以X& x = X{}
不行,X& x2 = std::move(x1)
也不行。
此外,f4(x)
不编译,因为 x
不是临时文件。表达式 (x)
实际上是 X&
。 X&&
只绑定临时的,比如X{}
或std::move(x)
。要将变量转换为临时变量,您需要使用 std::move
,它会执行转换。有关详细信息,请参阅 。
在下面的代码中,为什么允许 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;
}
为什么允许这样做背后的直觉是什么,std::function
将如何实现它,以及当您将左值参数传递给 std::function
时它的实际含义是什么,然后调用接受右值引用的函数?
如您所知,std::function
类型是任何类函数类型的多态包装器。这包括函数指针,类 with operator()
和 lambdas.
由于它必须实现类型擦除,它只检查您发送的内容是否可以使用它接收的参数调用。类型擦除将简单地隐式地进行转换,就像任何函数调用一样。
如果您查看 std::function::function
的 cppreference 页面,您会注意到此要求:
5) Initializes the target with
std::move(f)
. Iff
is a null pointer to function or null pointer to member,*this
will be empty after the call. This constructor does not participate in overload resolution unlessf
is Callable for argument typesArgs...
and return typeR
.
确实,X
类型的表达式完全可以绑定到 X&&
类型的右值引用。试试这个:
X&& x = X{};
// likewise, both prvalue and xvalue works:
f4(X{});
f4(std::move(x));
在此示例中,X{}
是 X
类型的纯右值。右值引用可以绑定到此类临时文件。
在std::function
内部,每个参数都被转发。转发顾名思义:它转发参数来调用包装函数,就像你直接调用它一样。然而,转发不保持纯右值,而是将参数作为 xvalue 转发。 xvalues 和 prvalues 都是右值的特例。
在 std::function
和转发的上下文中,将参数传递给包装函数对 X
和 X&&
使用相同的表达式。纯右值性不能在没有缺点的情况下转发。
要了解有关转发的更多信息,请参阅What are the main purposes of using std::forward and which problems it solves?
隐式转换可以更进一步:从 double
到 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;
}
这是可能的,因为 std::function
是函数对象的包装器。函数指针需要是准确的类型。
此外,f2
无法编译,因为对左值 (X&
) 的可变引用无法绑定临时变量。所以X& x = X{}
不行,X& x2 = std::move(x1)
也不行。
此外,f4(x)
不编译,因为 x
不是临时文件。表达式 (x)
实际上是 X&
。 X&&
只绑定临时的,比如X{}
或std::move(x)
。要将变量转换为临时变量,您需要使用 std::move
,它会执行转换。有关详细信息,请参阅