Rvalues, lvalues and formal definitions

2020-05-23 08:09发布

问题:

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?

  1. 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.
  2. 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.
  3. In the line 3, std::move(x) is an expression. Its type before adjustment is int&&, after - int. The value category is xvalue.
  4. 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.
  5. 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.
  6. 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.

回答1:

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.