为什么作为返回类型的右值引用不能初始化非常量引用?

Why rvalue reference as return type can't be initialization of non-const reference?

本文关键字:引用 不能 初始化 常量 非常 返回类型 为什么      更新时间:2023-10-16

我读了这个问题,我知道右值引用是一个左值。

但是,对于此代码(示例 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()不被视为右值,而它也只是一个没有名称的表达式?返回左值引用的函数的函数表达式和右值引用之间有什么区别?

我知道右值引用是一个左值。

你说的是两个不同的东西:类型和值类别。

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

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

以下表达式是 xvalue 表达式:

  • 函数调用或重载运算符表达式,其返回类型是对对象的 rvalue 引用,例如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

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

int && fun(){
return 1;
}

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

右值

引用如何成为右值?

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

然后最好回顾一下引用初始化规则:

第一个引用初始化规则指出,引用可以初始化("绑定")为与引用兼容的值。这意味着

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

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

第二个引用初始化规则指出const左值引用(const int&)和右值引用(int&&)可以绑定到:

  • x值或普尔值
  • 作为其他任何事情的最后手段

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

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

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

int & a=fun();

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

decltype(fun()) b=1;

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


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

是的。

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

不,这是一个左值引用。

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

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

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

右值引用是左值。

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

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

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


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

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

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 在他的回答中指出的那样,函数定义

int && fun(){
return 1;
} 

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

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