I am trying to understand what is going on in the code below.
The move constructor takes rvalue and assigns to the reference member.
Rvalue is unnamed and should be erased from memory, then how can it be assigned to the reference member?
Thank you!
#include <iostream>
class Test
{
public:
Test(int&& _m): m{_m}{}
int& m;
};
int main()
{
Test T(3);
std::cout << T.m << std::endl;
// this prints 3
}
CodePudding user response:
What you're seeing here is called Temporary Materialization.
This is where the simplification of value expressions into just "rvalue" and "lvalue" tends to break down and you need to actually start thinking about things like "xvalues".
From the reference:
A prvalue of any complete type T can be converted to an xvalue of the same type T. This conversion initializes a temporary object of type T from the prvalue by evaluating the prvalue with the temporary object as its result object, and produces an xvalue denoting the temporary object. If T is a class or array of class type, it must have an accessible and non-deleted destructor.
...
Temporary materialization occurs in the following situations:
when binding a reference to a prvalue;
You can see this in the AST for the expression as well:
| `-VarDecl 0x8b4ce50 <col:5, col:13> col:10 used T 'Test' callinit
| `-ExprWithCleanups 0x8b4d2a0 <col:10, col:13> 'Test'
| `-CXXConstructExpr 0x8b4d270 <col:10, col:13> 'Test' 'void (int &&)'
| `-MaterializeTemporaryExpr 0x8b4d258 <col:12> 'int' xvalue
| `-IntegerLiteral 0x8b4ceb8 <col:12> 'int' 3
To make good use of this information though, you also need to read about Temporary Object Lifetime.
TL;DR: you found a fancy way to make a dangling reference.
CodePudding user response:
The given program has undefined behavior as the parameter _m
(or the materialized temporary) will only persists until the constructor exits. This means once the constructor exits, the reference data member m
is now a dangling reference meaning it now refers to object that has gone out of scope.
This can be seen from class.temporary:
The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:
- A temporary bound to a reference member in a constructor's ctor-initializer ([class.base.init]) persists until the constructor exits.
(emphasis mine)
Basically, the temporary to which the reference member m
is bound will not persist until the lifetime of the referece member m
.
Thus, using T.m
in
//-----------vvv------------------->undefined behaivor
std::cout << T.m << std::endl;
is undefined behavior.