【问题标题】:How are std::move template parameters deduced?std::move 模板参数是如何推导出来的?
【发布时间】:2013-01-25 23:09:13
【问题描述】:

假设我们有:

foo(A&& a);

如果你这样做了

A a;
foo(a);

它不会编译并且抱怨不能将左值绑定到 A&&。没关系。

但是,鉴于 std::move 的签名,

template<class T> typename remove_reference<T>::type&& std::move(T&& a);

看起来它需要一个右值引用,就像在 foo 中一样,为什么下面的代码符合?

A a;
std::move(a);

a 不是左值吗?

另外,据说编译会实例化:

typename remove_reference<A&>::type&& std::move(A& && a);

我不明白为什么不是:

typename remove_reference<A>::type&& std::move(A && a);

在我看来 a 的类型是 A,而不是 A&amp;。

【问题讨论】:

    标签: c++ templates c++11


    【解决方案1】:

    不,move 不采用右值引用,它采用被社区称为通用引用的东西。类型推导的模板参数的行为根据引用折叠的规则。这意味着:

    • 如果T 是K,那么T&amp;&amp; 将只是K&amp;&amp;;
    • 如果T 是K&amp;,那么T&amp;&amp; 将折叠为K&amp;;
    • 如果T 是K&amp;&amp;,那么T&amp;&amp; 将折叠为T&amp;&amp;。

    这就像 &amp; 和 &amp;&amp; 的逻辑与,其中 &amp; 是 0,&amp;&amp; 是 1:

          &    &&
       |-----------|
    &  |  &  |  &  |
       |-----|-----|
    && |  &  | &&  |
       |-----------|
    

    这就是move 对右值和左值的作用。

    例子:

    template<typename T>
    void f(T&&);
    
    f<int>   // T is int;   plugging int into T makes int&& which is just int&&
    f<int&>  // T is int&;  plugging int& into T is int& && which collapse to int&
    f<int&&> // T is int&&; plugging int&& into T is int&& && which collapse to int&&
    

    请注意,引用折叠只发生在模板参数上;你不能直接输入int&amp;&amp; &amp;&amp; 并期望它编译。当然,您不会像那样手动指定类型。这些只是为了显示引用折叠到什么。

    所以你真的可以这样称呼它:

    int i;
    
    f(i); // T is int&;  int& && collapses to int&
    f(4); // T is int&&; int&& && collapses to int&&
    

    引用折叠也是move 不返回T&amp;&amp; 的原因:如果T 是左值引用并且使move 只返回一个左值引用,则引用将折叠。您执行remove_reference 以获取非引用类型,以便&amp;&amp; 真正表示“右值引用”。

    您可以在这里了解更多信息:http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers

    【讨论】:

      【解决方案2】:

      句法形式T&amp;&amp; 在类型推导的上下文中(包括模板参数推导,但例如也推导声明为的变量的类型auto) 不表示右值引用,而是 Scott Meyers 所说的 [通用引用]。请注意,只有非常特殊的句法形式 T&amp;&amp; 表示通用引用,而其他类似的形式不被视为通用引用。例如:

      template<typename T> 
      void foo(T&& t); <-- T&& is a universal reference
      
      template<typename T> 
      void foo(T const&& t); <-- T const&& is NOT a universal reference
      
      template<typename T> 
      void foo(S<T>&& t); <-- S<T>&& is NOT a universal reference
      
      template<typename T> 
      struct S { void foo(T&& t); }; <-- T&& is NOT a universal reference
      

      通用引用可以绑定到左值和右值。如果绑定了A 类型的左值,则T 被推断为A&amp;,并且由于引用折叠的规则,参数的类型解析为A&amp;(左值引用) >(A&amp; &amp;&amp; 变为 A&amp;)。如果绑定了A 类型的右值,则推导出T 为A,并且参数的类型解析为A&amp;&amp;(右值引用)。

      [注意:引用折叠规则可能看起来很复杂,但实际上非常简单:引用 Stephan T. Lavavej 的话,“左值引用具有传染性”,这意味着当表单 @ 987654335@、T&amp; &amp; 或 T&amp; &amp;&amp; 被实例化,它们总是解析为 T&amp; - 只有 T&amp;&amp; &amp;&amp; 的形式被解析为 T&amp;&amp;]

      这就是为什么std::move函数模板在参数为左值时会被实例化如下(T被推导出为T&amp;):

      typename remove_reference<A&>::type&& std::move(A& && a);
      

      而当参数为右值(T推导出为A)时,它会被实例化如下

      typename remove_reference<A>::type&& std::move(A&& a);
      

      【讨论】:

        【解决方案3】:

        尽管其他人说过,该标准只讨论右值引用。

        这对 std::move 起作用的关键是模板参数推导规则中的一个明确的特殊规则:

        [...] 如果 [声明的函数参数类型] 是右值引用 到一个 cv 不合格的模板参数并且参数是一个左值, 类型“对 A 的左值引用”用于代替类型的 A 扣除。[...]

        另一部分是引用折叠的规则,即

        如果 [...] 类型模板参数 [...] 表示类型 TR 是对 类型 T,尝试创建类型“对 cv TR 的左值引用” 创建类型“对 T 的左值引用”,同时尝试创建 类型“对 cv TR 的右值引用”创建类型 TR。

        现在在template&lt;class T&gt; typename remove_reference&lt;T&gt;::type&amp;&amp; std::move(T&amp;&amp; a); 中,函数参数 a 匹配上述规则(“对 cv 非限定模板参数的右值引用”),因此如果参数是左值,推导的类型将是对参数类型的左值引用。在您的情况下,这导致 T = A&。

        将其代入到 move 的声明中

        remove_reference<A&>::type&& std::move<A&>(A& && a);
        

        使用 remove_reference 的定义和引用折叠规则(对 TR => TR 的右值引用),使得:

        A&& std::move<A&>(A& a);
        

        Scott Meyer 的 universal reference 概念,正如在其他答案中提出的那样,是记住类型推导规则和引用折叠规则组合的这种令人惊讶的效果的有用方法:对 a 的右值引用推导的类型可能最终成为左值引用(如果该类型可以被推导为左值引用)。但是标准中没有通用参考。正如斯科特迈耶斯所说:这是一个谎言——但一个比真相更有帮助的谎言......

        请注意,std::forward 在这个主题上是一个不同的转折点:它使用额外的间接来防止参数推导(因此必须显式给出类型),但也使用引用折叠将左值转发为左值,右值转发为右值。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-02-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-12-27
          • 1970-01-01
          相关资源
          最近更新 更多