Reputation: 1781
There're lots of rvalue related quiestion but I didn't found answers to these exact questions.
I can't wrap my mind around "named reference is a lvalue reference" rule of thumb.
It seems really strange -- we declare the reference as an rvalue but since we have to somehow use this reference (otherwise, what's the point?), we name it and since it's named it's an lvalue after all!
Consider this code:
int&& foo(int&& arg)
{
arg = 10;
return arg; // ERROR, `arg` is an lvalue since it's named!
}
foo(1);
The questions are:
arg
-s converted from int&&
to int&
and then tries to implicitly convert int&
to int&&
which causes compilation error -- just the opposite behaviour! That's why we need std::move
which is basicaly an explicit static_cast to rvalue type.Upvotes: 2
Views: 746
Reputation: 1801
Question 3 asks why an implicit conversion from an lvalue (whether a reference or not) to an rvalue reference is not allowed. This could be based on the constraint that rvalue references can bind only to rvalues. This constraint is essential, for example, to overload functions in order to implement copy and move constructors.
About whether an expression can have a reference type, this blog post by Scott Meyers is very useful. The quote from the latest standard draft:
If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.
Thus, to be very accurate, an expression can have reference type but this reference-ness is removed for all practical purposes.
Upvotes: 0
Reputation: 13752
We should refine the "if it has a name it's an lvalue" rule, to the more exact form:
If it has a name, then when used as an expression it is an lvalue.
But don't we always use a variable as an expression?
Usually yes, but not necessarily. See the following function:
void foo(A&& a) {
// decltype of the variable a - is rvalue
static_assert(std::is_rvalue_reference_v<decltype(a)>);
// decltype of the expression a - is lvalue
static_assert(std::is_lvalue_reference_v<decltype((a))>);
}
Note that a
doesn't just become an lvalue. If it is being used as an expression - which is a common use for a variable - then the expression is an lvalue reference.
In the code above, the decltype
of a
is A&&
thus still an rvalue reference.
On the other hand, (a)
turns the use of a
into an expression in that context, thus the decltype
of (a)
is A&
- an lvalue reference.
You can think about that as: the expression that uses the variable a
, can be considered as an lvalue reference to A&&
(i.e. A&& &
), which with reference collapsing becomes A&
.
The fact that the variable a
is still an rvalue, and only the expression a
is an lvalue, is not so commonly in use. Usually we just deal with a
as an expression. However, it is being used when we want to forward an auto&&
parameter, e.g.:
[](auto&& v){ foo(std::forward<decltype(v)>(v)); } (A{});
From C++ Reference: An expression, can be categorized into one of three options:
Expressions that can be moved from are called "rvalue expressions". Both prvalues and xvalues are rvalue expressions. Since C++17, prvalues are no longer moved from, due to mandatory copy elision.
Expressions that have an identity are called "glvalue expressions" (glvalue stands for "generalized lvalue"). Both lvalues and xvalues are glvalue expressions.
Having an identity is considered as being possible to determine whether the expression refers to the same entity as another expression, such as by comparing addresses of the objects or the functions they identify.
In the following code we compare lvalue, prvalue and xvalue expressions:
void foo(A&& a) {
// below we send the expression a to bar, as lvalue
bar(a);
// std::move(a) is an xvalue expression
bar(std::move(a));
// A{} is a prvalue expression
bar(A{});
}
There is a list of expressions that may create a an xvalue (see the link below, for "What expressions create xvalues?"), but the most common two expressions for that are std::move
and std::forward
.
Creating a prvalue is easy, you just create an object without a name, or use an object in an expression that requires a prvalue (there is a conversion from lvalue to prvalue).
Code to play with: https://godbolt.org/z/so746KcqG
See also:
Upvotes: 3
Reputation: 47418
arg
the variable has type int&&
and no value category.
arg
the expression (it is an expression at lines 3 and 4) has type int
and value category "lvalue"
Lvalue to rvalue conversion changes the value category of an expression, without changing its type. If you write arg+1
inside the function, the lvalue expression arg
of type int
would undergo this conversion to produce a prvalue expression of type int
, since that's what built-in +
requires.
There are no "lvalue to rvalue" or reverse conversions between int& and int&& because expressions never have reference types. The error in your program is a failure to bind an rvalue reference (of type int&&) to an lvalue expression (of type int
).
Upvotes: 5