【问题标题】:Why rvalue reference as return type can't be initialization of non-const reference?为什么作为返回类型的右值引用不能初始化非常量引用?
【发布时间】:2018-11-05 19:23:15
【问题描述】:

我阅读了this 问题,我知道右值引用是左值。

但是,对于此代码,示例 1,

int &&fun() {
    return 1;
}

int main() {
    int &a = fun();
}

当我编译它时:

 error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'

所以 C++ 编译器告诉我 fun 的返回类型是一个右值。

右值引用如何变成右值?

我认为编译器应该以同样的方式对待左值引用和右值引用,但是这段代码,示例2,

int & fun(){
    int b;
    return b;
}
int main(){
    int & a=fun();
}

可以编译(不过,我收到警告)。

我认为fun 的返回类型可能在某些时候发生了变化。

尝试编译示例 3:

int &&fun() {
    return 1;
}

int main() {
    decltype(fun()) b = 1;
}

编译成功。所以我可以说fun的返回类型确实是一个右值引用。

那么,为什么右值引用会变成右值呢?

这里是示例 4:

int &&a = 1;
int &b = a;

它编译并告诉我们一个右值引用可以绑定到一个左值引用。

现在,那两个问题呢:

  1. 在示例 1 中,fun() 是右值吗?
  2. 在示例 1 中,fun() 是右值引用吗?

示例 3 告诉我们 fun() 是一个右值引用,示例 4 告诉我们一个右值引用可以绑定到一个左值引用(常量和非常量)。那为什么示例 1 中的 fun() 不能绑定到左值引用?

示例 4 也表明一个右值引用是一个左值,但是示例 1 中的编译错误告诉我们那里的fun(),在示例 3 中被证明是一个右值引用,是一个右值。那么,右值引用是左值还是右值?

如果原因是fun()只是一个表达式,它暂时存在,马上就会死掉,为什么示例2中的fun()不被视为右值,而它也只是一个没有名字的表达式?返回左值引用和右值引用的函数的函数表达式有什么区别?

【问题讨论】:

    标签: c++ return-type rvalue-reference rvalue lvalue


    【解决方案1】:

    非常量引用不能绑定到右值,就这么简单。

    int & a=fun();
    

    不起作用,因为a 是非常量引用,fun() 是右值表达式。
    在第二种情况下,fun() 返回一个非常量左值引用,当然可以绑定到另一个非常量引用。

    decltype(fun()) b=1;
    

    之所以有效,是因为decltype(fun())int &&,因此可以绑定到整数文字1


    在示例 1 中,fun() 是右值吗?

    是的。

    在示例 2 中,fun() 是右值引用吗?

    不,它是一个左值引用。

    示例 3 告诉我们 fun() 是一个右值引用,示例 4 告诉我们一个右值 引用可以绑定到左值引用(const 和 非常量)。那为什么不能将示例 1 中的 fun() 绑定到左值 参考?

    因为函数fun 返回一个右值引用,但fun() 本身是一个右值表达式fun() 是一个右值。

    示例 4 还表明右值引用是左值,但是 示例1中的编译错误告诉我们那里有fun(),这被证明是 示例 3 中的右值引用是一个右值。所以,是一个右值参考左值 还是右值?

    右值引用是左值。

    如果原因是fun()只是一个表达式,存在 暂时会马上死掉,为什么示例2中的fun()不是 认为是一个右值,而它也只是一个没有的表达式 一个名字?返回左值引用和右值引用的函数的函数表达式有什么区别?

    因为在示例 2 中,fun() 是一个左值。来自 N4296,§3.10/1.1:

    [...] 调用返回类型为左值的函数的结果 引用是一个左值。


    关于您收到的示例 2 的警告,您应该显示确切的消息。不过,这可能只是因为您返回了对局部变量的引用。局部变量的生命周期有限,因此在其生命周期之外引用它们是未定义的行为,因此会发出警告。

    【讨论】:

    • 我认为您遇到了一个冲突,即**右值引用是右值**(来自 Ex 1 和 Ex 3),与 stackoverflow.com/questions/28483250/… 有冲突。顺便说一句,我已经更详细地澄清了我的问题。谢谢。:)
    • @user482214 我没有提到右值引用是右值。我将根据您的编辑更新我的答案。
    【解决方案2】:

    我认为您正在混合 rvaluervalue reference。在你的第一个例子中

    int && fun(){
        // 1 is an rvalue so it can be bound to an rvalue reference
        // this will produce undefined behavior though because you
        // a returning a dangling reference to an temporary that will
        // go out of scope at the end of this function
        return 1;
    }
    int main(){
        // you are trying to take a reference to a temporary object.
        // this is (deliberately) not valid
        int & a=fun();
    
        // One legal way of doing this is by declaring your reference const:
        const int& b = fun(); 
        // because this extends the lifetime of the temporary object returned
        // by fun() to match the lifetime of the reference.
    }
    

    在你的第二个例子中:

    int & fun(){
        // you have allocated an new int in the free store so the 
        // lifetime of this int is until the main exits. The return
        // type here is an lvalue that can be safely bound to an 
        // lvalue reference
        return *(new int);
    }
    int main(){
        // binding lvalue reference to lvalue this is ok
        int & a=fun();
    }
    

    在你的第三个例子中

    int && fun(){
        // 1 is an rvalue and can be bound to an rvalue reference
        return 1;
    }
    int main(){
        // decltype(fun()) is equal to int&& so it's ok to bind
        // an rvalue reference to an rvalue
        decltype(fun()) b=1;
    }
    

    【讨论】:

    • 非常感谢我改变了我的 Ex 2 并添加了 Ex 4。首先,我认为在 Ex 2 中我可以返回一个 int 的引用,它的生命周期只能在函数范围内,所以它也是一个临时对象,与Ex 1中的相同。其次,我认为Ex 3告诉我们函数的返回类型是一个右值引用,它是一个左值并且可以绑定到一个左值引用(不同意Ex 1) .
    • @user482214 在 ex2 中使用 new 您正在将该值的寿命延长到函数范围之外,因此如果您不使用 delete 该值,您将得到一个 -叫memory leak。在示例 4 中,a 是一个左值,因为它有一个名称,我可以获取它的地址,因此可以将左值引用 b 绑定到恰好是右值引用的左值 (int&& a)。如果a 引用的临时对象发生了什么事,b 在当前范围内仍然持有对a 的有效引用。
    【解决方案3】:

    首先,这段代码表现出未定义的行为:

    int && fun(){
        return 1;
    }
    

    在这里,您返回了一个对1 的悬空引用,它超出了范围。

    右值引用如何变成右值?

    为了理解这一点,最好不要将引用视为指针的另一种语法,而是将其视为某些已存在对象的另一个 name

    那么最好复习一下引用初始化规则:

    first reference initialization rule 声明可以将引用初始化(“绑定”)为 引用兼容 值。这意味着

    • int& 可以绑定到int&
    • int&& 可以绑定到int&&
    • const int& 可以绑定到int&

    在这种情况下,右侧的实际引用不会被检索,而是直接绑定到新引用。 请注意,int&int&& 不兼容,它们是不同的类型。

    second reference initialization rule 声明 const 左值引用 (const int&) 和右值引用 (int&&) 可以绑定到:

    • xvalue 或prvalue
    • 作为最后的手段

    在后者的情况下,引用绑定到表达式的结果。在const int& x = fun() 的情况下,调用fun() 的结果将首先被“物化”(检索),然后将其 绑定到引用。

    但要做到这一点,左值引用必须是const。这就是错误指出非const int& 无法绑定到int 的原因,因为int 是评估fun() 的结果。

    【讨论】:

      【解决方案4】:

      我知道右值引用是左值。

      您在谈论两个不同的东西:type 和 value category。例如

      int&& ri = 0; // ri's type is rvalue reference (to int)
                    // ri's value category is lvalue; it's a named variable.
      

      给定您的第一个样本,fun() 返回的是一个 xvalue,它属于右值。

      以下表达式是 xvalue 表达式:

      • 函数调用或重载的运算符表达式,其返回类型为对对象的右值引用,如std::move(x)

      那么,

      int &a = fun(); // fails; lvalue-reference can't bind to rvalue
      

      在第二个示例中,fun() 返回的是一个左值,

      以下表达式是左值表达式:

      • 函数调用或重载的运算符表达式,其返回类型为左值引用,如std::getline(std::cin, str)std::cout << 1str1 = str2++it

      然后

      int & a=fun(); // fine; lvalue-reference could bind to lvalue
      

      在第三个样本中,

      decltype(fun()) b = 1; // the return type of fun() is rvalue-reference;
                             // this has nothing to do with the value category of its return value
                             // b's type is rvalue-reference too, btw its value category is lvalue
      

      在第 4 个样本中,

      int &&a = 1; // fine; rvalue-reference could bind to rvalue
                   // a's type is rvalue-reference, its value category is lvalue
      int &b = a;  // fine; lvalue-reference could bind to lvalue
                   // b's type is lvalue-reference, its value category is lvalue
      

      【讨论】:

      • 严格来说,值类别是表达式的属性,而不是变量的属性。
      【解决方案5】:

      关键是表达式的值类别不仅仅取决于它的类型,例如

      int&& a = 1;
      int&& fun();
      // int&& ri = a; // ill-formed, the expression a is of type int&&, but is an lvalue
      int&& ri = fun(); // ok, the expression fun() is of type int&&, and is also an rvalue
      

      另外,正如rustyx在his answer中指出的,函数定义

      int && fun(){
          return 1;
      } 
      

      可能会导致未定义的行为,因为临时对象将在 return 语句执行后立即销毁。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-12-08
        • 2020-05-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-07-31
        • 1970-01-01
        相关资源
        最近更新 更多