为什么rvalue引用作为返回类型不能初始化非const引用?

时间:2018-05-26 17:29:58

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

我读了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. 示例3告诉我们fun()是一个右值引用,示例4告诉我们一个右值引用可以绑定到左值引用(const和非const)。那么为什么不能将示例1中的fun()绑定到左值引用?

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

    如果原因是fun()只是一个临时存在并且会立即死亡的表达式,为什么示例2中的fun()不被视为右值,而它也只是一个表达式一个名字?返回左值引用的函数的函数表达式与右值引用之间有什么区别?

5 个答案:

答案 0 :(得分:3)

  

我知道右值参考是一个左值。

你在谈论两件不同的事情:打字和value category。 e.g。

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

鉴于您的第一个样本,fun()返回的是一个xvalue,它属于rvalues。

  

以下表达式是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

在第3个样本中,

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

答案 1 :(得分:1)

非const引用不能绑定到rvalues,就像那样简单。

int & a=fun();

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

decltype(fun()) b=1;

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

  

在示例1中,fun()是否是左值?

  

在示例2中,fun()是左值参考吗?

不,这是一个左值参考。

  

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

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

  

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

右值参考是左值。

  

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

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

  

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

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

答案 2 :(得分:1)

首先,此代码显示未定义的行为:

int && fun(){
    return 1;
}

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

  

右值参考如何成为左值?

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

然后讨论参考初始化规则是好的:

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()的结果。

答案 3 :(得分:1)

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

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

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

int && fun(){
    return 1;
} 

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

答案 4 :(得分:0)

我认为你正在混合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;
}