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

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

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

但是,对于此代码,示例 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 的 return 类型是右值。

右值引用如何变成右值?

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

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

可以编译(尽管如此,我收到警告)。

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

正在尝试编译示例 3:

int &&fun() {
    return 1;
}

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

编译成功。所以我可以说 fun 的 return 类型实际上是一个右值引用。

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

这是示例 4:

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

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

现在,那两个问题呢:

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

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

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

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

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

int & a=fun();

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

decltype(fun()) b=1;

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


In example 1, is fun() an rvalue?

是的。

In example 2, is fun() an rvalue reference?

不,它是一个左值引用。

Example 3 tells us that fun() is an rvalue reference and example 4 tells us an rvalue reference can be bound to an lvalue reference (both const and non-const). Then why can't fun() from example 1 be bound to an lvalue reference?

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

Example 4 also indicates that an rvalue reference is an lvalue, but the compilation error in example 1 tells us that fun() there, which is proved to be an rvalue reference in example 3, is an rvalue. So, is an rvalue reference lvalue or rvalue?

右值引用是左值。

If the cause is that fun() is just an expression, which exists temporarily and will die right away, why is the fun() in example 2 not regarded an rvalue while it is also just an expression without a name? What difference is there between a function expression of a function returning an lvalue reference and an rvalue reference?

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

[...] the result of calling a function whose return type is an lvalue reference is an lvalue.


关于您在示例 2 中收到的警告,您应该显示确切的消息。不过,这可能只是因为您 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;
}

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

int && fun(){
    return 1;
}

此处您将返回对 1 的悬空引用,这超出了范围。

How does a rvalue reference become an rvalue?

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

然后再看一遍参考初始化规则就好了:

first reference initialization rule 声明可以将引用初始化 ("bound") 为 reference-compatible 值。也就是说

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

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

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

  • xvalue 或 prvalue
  • 不得已而为之

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

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

的结果

I know that an rvalue reference is an lvalue.

你在谈论两个不同的东西:类型和 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() returns 是一个属于右值的 xvalue。

The following expressions are xvalue expressions:

  • a function call or an overloaded operator expression, whose return type is rvalue reference to object, such as std::move(x);

然后,

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

在第二个样本中,fun()returns是一个左值,

The following expressions are lvalue expressions:

  • a function call or an overloaded operator expression, whose return type is lvalue reference, such as std::getline(std::cin, str), std::cout << 1, str1 = str2, or ++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

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

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 语句执行后立即被销毁。