You're mixing up rvalues and temporaries. Temporaries are objects, rvalues are expressions.
The system of dividing expressions up into the categories lvalue, xvalue, prvalue is designed to help avoid logic errors in the program (for example, preventing 3 = 4;
) but it doesn't have any deeper meaning.
Any object (temporary or not) can be designated by an lvalue expression or an rvalue expression if we really want to . In the case in the question we make an xvalue expression that designates a
.
Then we initialize a reference, which binds directly since the type is the same (note: the type , int
, not the category).
The code has exactly the same effect as int& b = a;
with one single exception, the result of decltype(b)
.
The comment makes a good point as well -- temporary objects still exist in memory (in the abstract machine) , they are just a different storage class to other objects. And in the case of lifetime-extended temporaries they behave very much like objects of automatic storage.
Going the other way, you can have an lvalue expression designating a temporary object, int const& x = 5;
results in the lvalue expression x
designating a temporary that has had its lifetime extended.
Or even without lifetime extension, std::string{} = "x"
is an lvalue since operator=
returns lvalue reference. You could write auto z = (std::string{} = "x") = "y";
which is not a good code style of course but understanding it helps to understand the type and value category system .