重新绑定右值引用变量合法吗?



当在单个作用域中使用时,为什么这样编译:

float x = 3.14;
auto &&expr = 2*x + x*x; // expr binds to the result of an expression
expr = x;

演示首先,这看起来像一个引用重新绑定,这对于左值引用是非法的。除此之外,expr应该"是"。表达式的结果。这怎么可能改变呢?

你不能重新绑定顶层引用,就像你不能改变顶层const一样。但是当它们存在于类中时,从c++20开始就可以了。在c++20中,您可以重新绑定引用,但会遇到很多问题。它们(rvalue和lvalue ref)删除了默认的赋值操作符,因此必须编写自定义成员函数才能使这样的类普遍可用。

重新绑定左值refs只是将它绑定到另一个左值。右值引用绑定到没有名称的TMP,因此具有影响其引用的TMP对象生命周期的特殊特征。它们的生命周期在原始右值引用的范围结束时结束。然而,如果它们在一个类中,它们可以被反弹到一个不同的右值,这个类对象的作用域无关紧要。相反,是初始类对象的作用域结束了它的生命周期。

你不能重新绑定一个顶级引用,但是从c++20开始,你可以使用std::construct_at重新绑定一个存在于类中的引用。

示例lvalue ref:

#include <memory>
struct A { int& i; };
inline int f2(int i) {return 2 * i;}
int main() {
float x = 3.14f;
float&& expr = x * x + x * x; // expr binds to the result of an expression
expr = x; // this isn't a rebind, just an assignment to an rvalue reference
float& y = x;
// no way to rebind y. It's an lvalue reference
// and it cannot be changed to refer to another lvalue.
// However, lvalue references that are inside a class can
// be changed since an object of the class can be identified
int i1{ 1 };
int i2{ 2 };
// lvalue reference in a class
A a0{ i1 };
A a1{ i2 };
std::construct_at(&a0, a1); // a now contains a ref to i2;

下面的程序演示了如何重新绑定一个右值ref并不会延长ref的生命周期,即使它被反弹到的对象具有更长的生命周期。

#include <memory>
#include <iostream>
struct B {
inline static int seq{};
int i;
B() :i(seq++) {}
~B() {std::cout << "dtor " << i << "n";}
};
B f(){return B();}
struct A {
B&& rref;
};
int main() {
{
A a0{ f() };
std::cout << "a0.rref.i=" << a0.rref.i << "n";
{
A a1{ f() };
std::construct_at(&a0, std::move(a1)); // a0.rref's tmp now disconnected replaced by a1.rref
std::cout << "a0.rref.i=" << a0.rref.i << "n";
std::cout << "a1.rref.i=" << a1.rref.i << "n";
} // the tmp object referred to by both a0.rref and a1.rref, destroyed here when a1 goes out of scope
} // the original tmp, which a0.rref n longer aliases, is destroyed here when a0 coes out of scope
}
/* Outputs *
a0.rref.i=0
a0.rref.i=1
a1.rref.i=1
dtor 1
dtor 0
***********/

感谢@HolyBlackCat让我明白右值裁判确实可以被篮板。它们甚至可以引用相同的tmp对象。但要注意时间。

另一个注意事项,与左值引用不同,我想不出在类中重新绑定右值引用的用例。在类中的左值引用很少使用