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:
- if it has an identity and cannot be moved from, it is called an lvalue expression
- if it has an identity and can be moved from, it is called an xvalue expression
- if it doesn't have an identity and can be moved from, it is called a prvalue ("pure rvalue") expression
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: