为什么模板参数推导被禁用 std::forward
Why is template argument deduction disabled with std::forward?
在VS2010中,std::forward被定义为:
template<class _Ty> inline
_Ty&& forward(typename identity<_Ty>::type& _Arg)
{ // forward _Arg, given explicitly specified type parameter
return ((_Ty&&)_Arg);
}
identity
似乎仅用于禁用模板参数推断。在这种情况下故意禁用它有什么意义?
X
对象的右值引用传递给以类型 T&&
作为其参数的模板函数,则模板参数推导会推断出T
X
。因此,该参数的类型为 X&&
。如果函数参数是左值或常量左值,编译器会将其类型推断为该类型的左值引用或常量左值引用。
如果std::forward
使用了模板参数推导:
由于objects with names are lvalues
std::forward
唯一正确转换为T&&
的时间是当输入参数是未命名的右值(如7
或func()
(时。在完美转发的情况下,您传递给std::forward
arg
是一个左值,因为它有一个名称。 std::forward
的类型将被推导出为右值引用或常量左值引用。引用折叠规则将导致 std::forward 中static_cast<T&&>(arg)
中的T&&
始终解析为左值引用或常量左值引用。
例:
template<typename T>
T&& forward_with_deduction(T&& obj)
{
return static_cast<T&&>(obj);
}
void test(int&){}
void test(const int&){}
void test(int&&){}
template<typename T>
void perfect_forwarder(T&& obj)
{
test(forward_with_deduction(obj));
}
int main()
{
int x;
const int& y(x);
int&& z = std::move(x);
test(forward_with_deduction(7)); // 7 is an int&&, correctly calls test(int&&)
test(forward_with_deduction(z)); // z is treated as an int&, calls test(int&)
// All the below call test(int&) or test(const int&) because in perfect_forwarder 'obj' is treated as
// an int& or const int& (because it is named) so T in forward_with_deduction is deduced as int&
// or const int&. The T&& in static_cast<T&&>(obj) then collapses to int& or const int& - which is not what
// we want in the bottom two cases.
perfect_forwarder(x);
perfect_forwarder(y);
perfect_forwarder(std::move(x));
perfect_forwarder(std::move(y));
}
因为std::forward(expr)
没有用。它唯一能做的就是无操作,即完美地转发其参数并像恒等函数一样行事。另一种选择是它与 std::move
相同,但我们已经有了。换句话说,假设这是可能的,在
template<typename Arg>
void generic_program(Arg&& arg)
{
std::forward(arg);
}
std::forward(arg)
在语义上等同于arg
。另一方面,在一般情况下,std::forward<Arg>(arg)
不是无操作。
因此,通过禁止std::forward(arg)
它有助于捕获程序员的错误,我们没有任何损失,因为任何可能的std::forward(arg)
使用都被arg
轻松取代。
我认为如果我们专注于std::forward<Arg>(arg)
到底做了什么,而不是std::forward(arg)
会做什么(因为这是一个无趣的禁操作(,你会更好地理解事情。让我们尝试编写一个完美地转发其参数的无操作函数模板。
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return arg; }
这种幼稚的第一次尝试并不完全有效。如果我们称noop(0)
那么NoopArg
被推导出为int
。这意味着返回类型是int&&
的,我们不能从表达式 arg
绑定这样的右值引用,这是一个左值(它是一个参数的名称(。如果我们随后尝试:
template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return std::move(arg); }
然后int i = 0; noop(i);
失败。这一次,NoopArg
被推导为int&
(引用折叠规则保证int& &&
折叠为int&
(,因此返回类型为int&
,这次我们不能从表达式std::move(arg)
绑定这样的左值引用,这是一个xvalue。
在像这样的完美转发函数的上下文中 noop
,有时我们想移动,但有时我们不想。知道我们是否应该移动的规则取决于Arg
:如果它不是左值引用类型,则意味着noop
传递了一个右值。如果是左值引用类型,则表示noop
传递了左值。所以在std::forward<NoopArg>(arg)
中,NoopArg
是为了使函数模板做正确的事情而std::forward
的必要参数。没有它,就没有足够的信息。此NoopArg
与一般情况下推导std::forward
的T
参数的类型不同。
简短回答:
因为要使std::forward
按预期工作(,即轻松传递原始类型信息(,它应该在模板上下文中使用,并且它必须使用从封闭模板上下文中推导出的类型参数,而不是自己推断类型参数(,由于只有封闭模板才有机会推断出真实的类型信息,这将在细节中解释(,因此必须提供类型参数。
尽管可以在非模板上下文中使用std::forward
,但它毫无意义(将在详细信息中解释(。
如果有人敢于尝试实现std::forward
以允许类型推断,他/她注定要痛苦地失败。
详:
例:
template <typename T>
auto someFunc(T&& arg){ doSomething(); call_other_func(std::forward<T>(para)); }
观察者认为arg
被声明为 T&&
,(它是推断传递的真实类型的关键,并且(它不是右值引用,尽管它具有相同的语法,但它被称为通用引用(Scott Meyers 创造的术语(,因为T
是泛型类型,(同样,在string s; auto && ss = s;
ss 不是右值引用(。
由于通用引用,当实例化someFunc
时,某些类型推导出魔术会发生,具体如下:
- 如果将类型为
_T
或_T &
的 rvalue 对象传递给someFunc
,T
将被推导出为_T &
(是的,即使X
的类型只是_T
,请阅读 Meyers 的文章(; - 如果将类型为
_T &&
的右值传递给someFunc
,T
将被推导出为_T &&
现在,您可以将T
替换为上面代码中的 true 类型:
当传递左值 obj 时:
auto someFunc(_T & && arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
在应用引用折叠规则(,请阅读迈耶斯的文章(后,我们得到:
auto someFunc(_T & arg){ doSomething(); call_other_func(std::forward<_T &>(arg)); }
当传递右值 obj 时:
auto someFunc(_T && && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
在应用引用折叠规则(,请阅读迈耶斯的文章(后,我们得到:
auto someFunc(_T && arg){ doSomething(); call_other_func(std::forward<_T &&>(arg)); }
现在,你可以猜到std::forward所做的只是static_cast<T>(para)
(实际上,在clang 11的实现中它是static_cast<T &&>(para)
的,在应用引用折叠规则后是一样的(。一切都很好。
但是如果你考虑让 std::fowrd 自己推导出类型参数,你很快就会发现在 someFunc
内部, std::forward
字面上无法推导出原始类型的arg
。
如果你试图让编译器这样做,它永远不会被推导出为_T &&
(,是的,即使arg
绑定到一个_T &&
,它仍然是someFunc
内部的 lvaule obj,因此只能推断为_T
或_T &
......你真的应该阅读迈耶斯的文章(。
最后,为什么只在模板中使用std::forward
?因为在非模板上下文中,您确切地知道您拥有哪种类型的 obj。因此,如果您将左值绑定到右值引用,并且需要将其作为 lvaule 传递给另一个函数,只需传递它,或者如果您需要将其作为右值传递,只需执行std::move
。你根本不需要在非模板上下文中输入 std::forward。
- 完美前进使用 std::forward vs RefRefCast
- 你如何理解"std: :forward is just syntactic sugar"?这是真的吗?
- "std::forward"和"std::move"真的不生成代码吗?
- 在C++中使用 std::forward 的多个参数
- 如何使用 std::forward 精确地评估参数包的扩展?
- 如何通过通用引用或std::forward将这三个c++模板函数合并为一个
- 自 C++11 年以来对 std::forward 实施的理解
- 当将参数包传递给另一个可变参数模板函数时,我是否必须使用 std::forward
- 为什么要在概念中使用std::forward
- 寻求对std::forward的澄清
- 为什么 std::forward 将左值和右值转换为右值引用?
- std::forward 如何推断出"_Ty"的类型?
- std::function operator() 和 std::forward 中发生了什么?
- How to std::forward( *this )
- 在哪些情况下,从 std::forward 分配比从 std::move 分配更可取?为什么
- 如何在varadic/template类构造函数中正确使用std::forward
- 包含 std::forward 会产生错误,但其省略会编译.为什么
- Returning std::make_pair with std::forward
- C 11构造函数参数:std ::移动和值或std :: forward and rvalue参考
- std::forward and operator()