【问题标题】:Why an Rvalue Reference is Turned into Lvalue Reference by a Universal Reference为什么右值引用被通用引用变成左值引用
【发布时间】:2016-12-28 11:13:29
【问题描述】:

我想当通用引用参数与右值引用参数匹配时,会返回一个右值引用参数。但是,我的测试表明,右值引用被通用引用函数模板变成了左值引用。为什么会这样?

#include <iostream>
#include <type_traits>
using namespace std;
template <typename T>
T f1(T&&t) {  //<-----this is a universal reference
  cout << "is_lvalue reference:" << is_lvalue_reference<T>::value << endl;
  cout << "is_rvalue reference:" << is_rvalue_reference<T>::value << endl;
  cout << "is_reference:"        << is_reference<T>::value        << endl;
  return t;
}
void f2(int&& t) {
  cout << "f2 is_lvalue reference:" << is_lvalue_reference<decltype(t)>::value << endl;
  cout << "f2 is_rvalue reference:" << is_rvalue_reference<decltype(t)>::value << endl;
  cout << "f2 is_reference:" << is_reference<decltype(t)>::value << endl;
  f1(t);

}

int main()
{
  f2(5);
  return 0;
}

在 GCC 和 VC++2010 中,结果如下:

f2 is_lvalue reference:0
f2 is_rvalue reference:1
f2 is_reference:1
is_lvalue reference:1
is_rvalue reference:0
is_reference:1

也就是说,f2 中的参数t 是一个右值引用,但是当传递给f1 时,该参数变成了一个左值引用。它不应该保留f1 中的右值吗?

【问题讨论】:

标签: c++ c++11 rvalue-reference perfect-forwarding lvalue-to-rvalue


【解决方案1】:

原因是命名的右值引用被视为左值。

在将 t 传递给 f1 时,您应该在 f2 中使用 std::move 以保留右值性:

void f2(int&& t) {
    f1(std::move(t));
}

Here你可以找到一个很好的解释。

【讨论】:

  • 你分享的链接最后有一个例子Derived(Derived&amp;&amp; rhs) : Base(std::move(rhs)) // good, calls Base(Base&amp;&amp; rhs) { // Derived-specific stuff }。现在,假设 class Derived 有 data_member int x;。请指定如何在分配this-&gt;x=rhs.x 中使用 rhs。由于它是一个复制构造函数,所以Derived 数据需要从rhs 复制到this。但是由于rhsmoved 所以,我想它不再有效了。
  • 好的,std::move() 将其参数转换为右值。但是这个表达式std::move(t) 形成的右值的类型是什么? int&amp;&amp; 类型的右值或int 类型的右值?
  • @JavaMan 右值类型为int。只有表达式有值类别,表达式不能有引用类型。
【解决方案2】:

调用f1(t),参数是表达式t。不是static_cast&lt;decltype(t)&gt;(t) 什么的。你对decltype(t)的检查与f1(t)的调用无关。

表达式t 具有int 类型和值类别lvalue。 (经验法则是,如果你可以获取一个表达式的地址,那么它就是一个左值,你当然可以写成&amp;t)。引用变量最初被声明为引用的“信息”只能通过decltype 检查看到。

由于f1 是用左值调用的,所以T 被推导出为int&amp;

注意。如果您想看到is_rvalue_referencef1 中为真,您可能希望f1 也使用decltype(t) 而不是T。对于右值参数,T 推断为非引用类型,例如如果你通过修复f2 来修复f1(std::move(t)); 那么f1Tintdecltype(t)f1int&amp;&amp;

【讨论】:

    【解决方案3】:

    在学习了 C++11 标准之后,我对我的 f1(t);f2 之后发生的事情有了一个模糊的概念。我在这里描述一下,看看我是否正确:

    1. f2中,tint&amp;&amp;类型的左值(不是int,这是一个重要的区别)
    2. 调用f1(t); 导致类型被推导如下:

      2.1 当f1 中的T 被赋予一个左值时,它被推断为该左值类型或int&amp;&amp; &amp; 的引用

      2.2 引用折叠导致int&amp;&amp; &amp; 变为int &amp;。这是T 的值。

    3. 由于f1的参数声明为T&amp;&amp;,所以f1中的参数t的类型为int &amp; &amp;&amp;。所以引用折叠第二次发生以推断t的类型为int &amp;

    4. 因此 T=int &amp; 和参数类型 tint &amp;。即参数tint &amp;类型的左值

    有什么意见吗?

    【讨论】:

    • 第2步中没有引用折叠。变量t的类型是int&amp;&amp;,而表达式的类型tint。这就是引用的工作方式,并且始终有效,无论值类别如何,甚至在将右值引用添加到语言之前。
    • 在这种情况下,第 2 步中T 的值是多少?不应该是int&amp;&amp; &amp; 而不是int &amp; 吗?如果T是int&amp;&amp; &amp;,是不是得把它折叠起来才能得到参数类型?
    • T 不参与步骤 2。f1 使用类型为 int 的左值表达式调用,因此在步骤 3 中,T 被推导为 int&amp;,因此 @ 987654359@ 是 int&amp; &amp;&amp; 折叠成 int&amp;
    【解决方案4】:

    使用 std::forward 将 t 保留为右值。

    void f2(int&& t) {
      //....
      f1(std::forward<int>(t));
    }
    

    t 参数作为右值传递并推导出为 int 类型。 (看扣分是怎么做的here

    std::forward(t) 返回一个 int&& 类型的右值,以便调用 f1(int&&)

    没有 std::forward(t),f1(t) 接受 int 类型的参数并调用 f1(int&)

    更新:请注意两者的区别

     is_lvalue_reference<T> and is_lvalue_reference<decltype<t>>. 
    

    在模板中,T是根据函数参数推导出来的,而t的值类别总是f2中的左值,而

    forward<int>(t) and move(t) 
    

    总是右值。

    #include <iostream>
    #include <type_traits>
    
    class A {};
    
    
    template <typename T>
    T f0(T& t) {  //<-----this is a universal reference
        std::cout<< "f0 lvalue"<<'\n';
    
        // take T as template argument, it is type int, so not rvalue, not lvalue.
    //  std::cout << "is_lvalue reference:" << std::is_lvalue_reference<T>::value << std::endl;
        std::cout << "is_lvalue reference:" << std::is_lvalue_reference<T>::value << std::endl;
        std::cout << "is_rvalue reference:" << std::is_rvalue_reference<T>::value << std::endl;
        std::cout << "is_reference:"        << std::is_reference<T>::value        << std::endl;
        return t;
    }
    
    template <typename T>
    T f0(T&&t) {  //<-----this is a universal reference
        std::cout<< "f0 rvalue"<<'\n';
        std::cout << "is_lvalue reference:" << std::is_lvalue_reference<T>::value << std::endl;
        std::cout << "is_rvalue reference:" << std::is_rvalue_reference<T>::value << std::endl;
        std::cout << "is_reference:"        << std::is_reference<T>::value        << std::endl;
        return t;
    }
    
    template <typename T>
    T f1(T& t) {  //<-----this is a universal reference
        std::cout<< "f1 lvalue"<<'\n';
    
        // take T as template argument, it is type int, so not rvalue, not lvalue.
    //  std::cout << "is_lvalue reference:" << std::is_lvalue_reference<T>::value << std::endl;
        std::cout << "is_lvalue reference:" << std::is_lvalue_reference<decltype(t)>::value << std::endl;
        std::cout << "is_rvalue reference:" << std::is_rvalue_reference<decltype(t)>::value << std::endl;
        std::cout << "is_reference:"        << std::is_reference<decltype(t)>::value        << std::endl;
        return t;
    }
    
    template <typename T>
    T f1(T&&t) {  //<-----this is a universal reference
        std::cout<< "f1 rvalue"<<'\n';
        std::cout << "is_lvalue reference:" << std::is_lvalue_reference<decltype(t)>::value << std::endl;
        std::cout << "is_rvalue reference:" << std::is_rvalue_reference<decltype(t)>::value << std::endl;
        std::cout << "is_reference:"        << std::is_reference<decltype(t)>::value        << std::endl;
        return t;
    }
    
    void f2(int&&t) {  //<-----this is a universal reference
        std::cout<< "f2 rvalue"<<'\n';
        std::cout << "is_lvalue reference:" << std::is_lvalue_reference<decltype(t)>::value << std::endl;
        std::cout << "is_rvalue reference:" << std::is_rvalue_reference<decltype(t)>::value << std::endl;
        std::cout << "is_reference:"        << std::is_reference<decltype(t)>::value        << std::endl;
    
    
        f1(std::forward<int>(t));   // T is deduced as int for f(T&&), type int is not rvalue, nor lvalue, t is rvalue
        f1(std::move(t));           // T is deduced as int for f(T&&), type int is not rvalue, nor lvalue, t is rvalue
        f1(t);                      // T is deduced as int for f(T&),  type int is not rvalue, nor lvalue, t is lvalue
                                    //if f1(T&) not exist, then f1(t) will call f1(T&&), T is deduced as int&, type int& is lvalue, t is lvalue
    
    
        f0(std::forward<int>(t));   // T is deduced as int for f(T&&), type int is not rvalue, nor lvalue, t is rvalue
        f0(std::move(t));           // T is deduced as int for f(T&&), type int is not rvalue, nor lvalue, t is rvalue
        f0(t);                      // T is deduced as int for f(T&),  type int is not rvalue, nor lvalue, t is lvalue
                                    //if f0(T&) not exist, then f0(t) will call f0(T&&), T is deduced as int&, type int& is lvalue, t is lvalue
    }
    
    
    
    
    void test_rvalue()
    {
    
        f2(5);
        std::cout << std::boolalpha;
        std::cout << std::is_lvalue_reference<A>::value << '\n';        // A is not lvalue
        std::cout << std::is_rvalue_reference<A>::value << '\n';        // A is not rvalue
        std::cout << std::is_lvalue_reference<A&>::value << '\n';       // A& is lvalue
        std::cout << std::is_rvalue_reference<A&&>::value << '\n';      // A&& is rvalue
        std::cout << std::is_lvalue_reference<int>::value << '\n';      // int is not lvalue
        std::cout << std::is_rvalue_reference<int>::value << '\n';      // int is not rvalue
        std::cout << std::is_lvalue_reference<int&>::value << '\n';     // int& is lvalue
        std::cout << std::is_rvalue_reference<int&&>::value << '\n';    // int&& is rvalue
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-13
      • 1970-01-01
      • 2017-04-15
      • 2022-08-17
      • 1970-01-01
      相关资源
      最近更新 更多