为什么用std :: forward禁用模板参数推导?

时间:2011-10-15 18:46:38

标签: c++ visual-studio-2010 stl c++11

在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似乎仅用于禁用模板参数推断。在这种情况下有意禁用它的重点是什么?

3 个答案:

答案 0 :(得分:21)

如果将对X类型的对象的rvalue引用传递给以T&&类型为参数的模板函数,则模板参数推导将T推导为X }。因此,参数的类型为X&&。如果函数参数是左值或左值,则编译器将其类型推导为该值的左值引用或常量值引用。

如果std::forward使用模板参数扣除:

由于objects with names are lvalues唯一一次std::forward正确转换为T&&,因此输入参数是未命名的右值(如7func()) 。在完美转发的情况下,传递给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)中,NoopArgstd::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超载完全匹配。