People are confused when they hear that in
int&& x
x
has rvalue reference type, but x
is an lvalue. Misunderstanding stems from the fact that identifiers and expressions are different things, and so are types and value categories. Moreover, types of expressions are "adjusted prior to any further analysis", and the words "rvalue" and "lvalue" can appear both in type name and in value category name.
I want to clarify formal definitions. Suppose we have a function:
1 | void f(int&& x) {
2 | ... = x;
3 | ... = std::move(x);
4 | }
Are the following statements correct?
- In the line 1,
x
is an identifier (id-expression) that names a function parameter. Its type is int&&
, and this is the type that decltype(x)
returns. x
is not an expression and has no value category.
- In the line 2,
x
is an expression. Before type adjustment its type is int&&
, and after the type becomes int
. The value category is lvalue.
- In the line 3,
std::move(x)
is an expression. Its type before adjustment is int&&
, after - int
. The value category is xvalue.
- When we say that
x
has rvalue reference type, we refer either to the type of x
as an identifier, or to the type of x
as an expression before type adjustment.
- The word "type" in the statement "Each expression has some non-reference type, and each expression belongs to exactly one of the three primary value categories" at cppreference.com refers to the type after type adjustment.
- When Scott Meyers writes "If the type of an expression is an lvalue reference (e.g.,
T&
or const T&
, etc.), that expression is an lvalue." he refers to the type before adjustment, and the second word "lvalue" refers to the value category.
Some preliminary paragraphs first:
[basic]
3 An entity is a value, object, reference, function, enumerator,
type, class member, template, template specialization, namespace,
parameter pack, or this.
[dcl.type.simple]
4 The type denoted by decltype(e)
is defined as follows:
if e
is an unparenthesized id-expression or an unparenthesized class member access ([expr.ref]), decltype(e)
is the type of the
entity named by e
. If there is no such entity, or if e names a set
of overloaded functions, the program is ill-formed;
otherwise, if e
is an xvalue, decltype(e)
is T&&
, where T
is the type of e
;
otherwise, if e is an lvalue, decltype(e)
is T&
, where T
is the type of e
;
otherwise, decltype(e)
is the type of e
.
[dcl.ref]
1 In a declaration T D
where D
has either of the forms
& attribute-specifier-seqopt D1
&& attribute-specifier-seqopt D1
and the type of the identifier in the declaration T D1
is
“derived-declarator-type-list T
,” then the type of the identifier
of D
is “derived-declarator-type-list reference to T
.”
[expr]
5 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.
[expr.prim.general]
8 An identifier is an id-expression provided it has been suitably
declared (Clause [dcl.dcl]). The type of the expression is the type of
the identifier. The result is the entity denoted by the identifier.
The result is an lvalue if the entity is a function, variable, or data
member and a prvalue otherwise.
[expr.call]
10 A function call is an lvalue if the result type is an lvalue
reference type or an rvalue reference to function type, an xvalue if
the result type is an rvalue reference to object type, and a prvalue
otherwise.
Which now allows us to answer your questions.
In the line 1, x
is an identifier (id-expression) that names a function parameter. Its type is int&&
, and this is the type that decltype(x)
returns. x
is not an expression and has no value category.
Yes of sorts. x
in the declaration is not an expression. But as an argument to decltype
is an expression. However, it hits the special case of decltype
's first bullet so the type of the identifier named by x
is deduced, instead of the type of x
as an expression.
In the line 2, x
is an expression. Before type adjustment its type is int&&
, and after the type becomes int
. The value category is lvalue.
Yes.
In the line 3, std::move(x)
is an expression. Its type before adjustment is int&&
, after - int
. The value category is xvalue.
Yes.
When we say that x
has rvalue reference type, we refer either to the type of x
as an identifier, or to the type of x
as an expression before type adjustment.
Yes.
The word "type" in the statement "Each expression has some non-reference type, and each expression belongs to exactly one of the three primary value categories" at cppreference.com refers to the type after type adjustment.
Yes.
When Scott Meyers writes "If the type of an expression is an lvalue reference (e.g., T&
or const T&
, etc.), that expression is an lvalue." he refers to the type before adjustment, and the second word "lvalue" refers to the value category.
Can't really say for sure what Scott Meyers meant when he wrote this, but that is the only interpretation of the words that matches up with the standard, yes.