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

Dav*_*vid 24 c++ stl visual-studio-2010 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);
}
Run Code Online (Sandbox Code Playgroud)

identity似乎仅用于禁用模板参数推断.在这种情况下故意禁用它有什么意义?

Dav*_*vid 24

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

如果std::forward使用模板参数推导:

由于输入参数是未命名的右值(如或),objects with names are lvalues因此唯一的时间std::forward将被正确转换T&&为.在完美转发的情况下,您传递给左值是因为它有一个名称.的类型将推导为左值引用或const左值引用.参考折叠规则会导致std :: forward 中的in始终解析为左值引用或const左值引用.7func()argstd::forwardstd::forwardT&&static_cast<T&&>(arg)

例:

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));
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,仅删除`remove_reference`即可使所有工作正常-只要`forward`接受`&`,并且在调用时保留显式模板规范即可。要使这项工作有效,必须使用明确的模板规范,因此`remove_reference`确保我们不会忘记它。您的带有转发参考(`&amp;&amp;`)的示例是红色鲱鱼,恕我直言 (3认同)

Luc*_*ton 17

因为std::forward(expr)没用.它唯一能做的就是无操作,即完美地转发它的论证,并且像一个身份函数.替代方案是,它是相同的std::move,但我们已经有了.换句话说,假设它是可能的,在

template<typename Arg>
void generic_program(Arg&& arg)
{
    std::forward(arg);
}
Run Code Online (Sandbox Code Playgroud)

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; }
Run Code Online (Sandbox Code Playgroud)

这种天真的第一次尝试并不十分有效.如果我们打电话,noop(0)那么NoopArg推断为int.这意味着返回类型是int&&,我们不能从表达式绑定这样的右值引用arg,这是一个左值(它是参数的名称).如果我们再尝试:

template<typename NoopArg>
NoopArg&& noop(NoopArg&& arg)
{ return std::move(arg); }
Run Code Online (Sandbox Code Playgroud)

然后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会在一般情况下推断.

  • 那么 `std::forward&lt;T&gt;(t)` 与 `std::forward(t)` 有什么不同呢? (2认同)
  • 我不明白,如果它推导了模板参数类型而不是我明确给出它,那为什么将是一个空操作。您能详细说明一下吗? (2认同)
  • @avakar No.由于我们将变量传递给`std :: forward`,因此`T`将被推导为左值引用类型,并且引用折叠规则将确保返回类型是相同的左值引用类型,因此调用将是一个左值.类似地,`std :: forward(0)`将是一个xvalue.因此,推断出`std :: forward`将完美地转发其论证并成为无操作. (2认同)
  • @戴夫 对的。但是 `t` 不用作右值引用,我不确定你为什么提到这一点。您的意思是绑定到`std::forward`的参数吗?您是否理解在 `template&lt;typename T&gt; void foo(T&amp;&amp;);` 中,`T&amp;&amp;` 可能是也可能不是右值引用? (2认同)