在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
似乎仅用于禁用模板参数推断。在这种情况下有意禁用它的重点是什么?
答案 0 :(得分:21)
如果将对X
类型的对象的rvalue引用传递给以T&&
类型为参数的模板函数,则模板参数推导将T
推导为X
}。因此,参数的类型为X&&
。如果函数参数是左值或左值,则编译器将其类型推导为该值的左值引用或常量值引用。
如果std::forward
使用模板参数扣除:
由于objects with names are lvalues
唯一一次std::forward
正确转换为T&&
,因此输入参数是未命名的右值(如7
或func()
) 。在完美转发的情况下,传递给arg
的{{1}}是左值,因为它有一个名称。 std::forward
的类型将被推导为左值引用或const左值引用。参考折叠规则会导致std :: forward中std::forward
中的T&&
始终解析为左值引用或const左值引用。
示例:强>
static_cast<T&&>(arg)
答案 1 :(得分:17)
因为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
不与T
std::forward
参数在一般情况下推断的类型相同。
答案 2 :(得分:7)
编辑:以下内容展示了我困惑的根源。请 解释为什么它调用somefunc#1而不是somefunc#2。
template<typename T> T&& forward(T& x) { return static_cast<T&&>(x); } void somefunc( int& ){} // #1 void somefunc( int&& ){} // #2 template<typename T> void ForwardingFunc(T&& x) { somefunc(forward(x)); } int main() { ForwardingFunc(5); }
ForwardingFunc x
始终在每次使用时都被视为左值:
somefunc(forward(x));
名称对象始终为左值。
这是一项关键的安全功能。如果你有一个名字,你不想隐含地从它移动:从它移动两次太容易了:
foo(x); // no implicit move
bar(x); // else this would probably not do what you intend
在对forward
的调用中,T
推断为int&
,因为参数x
是左值。所以你称之为专业化:
template<>
int& forward(int& x)
{
return static_cast<int&>(x);
}
因为T
推断为int&
而int& &&
折叠会回归到int&
。
由于forward(x)
返回int&
,对somefunc
的调用与#1超载完全匹配。