右值、左值和形式定义

人们在听到这句话时会感到困惑

int&&X

x 具有右值引用类型,但 x 是左值.误解源于这样一个事实,即标识符和表达式是不同的东西,类型和值类别也是如此.此外,表达式的类型是在进一步分析之前进行调整",rvalue"和lvalue"这两个词可以出现在类型名称和值类别名称中.

我想澄清正式定义.假设我们有一个函数:

1 |void f(int&& x) {2 |... = x;3 |... = std::move(x);4 |}

下列说法正确吗?

  1. 在第 1 行中,x 是命名函数参数的标识符(id 表达式).它的类型是 int&&,这是 decltype(x) 返回的类型.x 不是表达式,也没有值类别.
  2. 在第 2 行中,x 是一个表达式.类型调整前,其类型为int&&,类型调整后为int.值类别是左值.
  3. 在第 3 行中,std::move(x) 是一个表达式.其调整前的类型是int&&,后-int.值类别是 xvalue.
  4. 当我们说 x 具有右值引用类型时,我们要么将 x 的类型称为标识符,要么将 x 的类型称为code> 作为类型调整前的表达式.
  5. cppreference.com 指的是类型调整后的类型.
  6. 当 Scott Meyers 写 时如果表达式的类型是左值引用(例如,T&const T& 等),则该表达式是左值."he指的是调整前的类型,第二个lvalue"指的是值类别.

解决方案

先说一些初步的段落:

<块引用>

[基本]

3 实体是值、对象、引用、函数,枚举器,类型、类成员、模板、模板特化、命名空间、参数包,或者这个.

[dcl.type.simple]

4 decltype(e) 定义如下:

  • 如果 e 是一个没有括号的 id-expression 或者一个没有括号的类成员访问 ([expr.ref]),decltype(e) 是类型由 e 命名的实体.如果没有这样的实体,或者如果 e 命名了一个集合重载函数,程序格式错误;

  • 否则,如果 e 是 xvalue,decltype(e)T&&,其中 Te 的类型;

  • 否则,如果 e 是左值,则 decltype(e)T&,其中 TT 的类型代码>e;

  • 否则,decltype(e)就是e的类型.

[dcl.ref]

1 在声明 TD 其中 D 有两种形式

<前>& attribute-specifier-seqopt D1&& attribute-specifier-seqopt D1

并且声明T D1中标识符的类型是derived-declarator-type-list T",然后是标识符的类型D 是derived-declarator-type-list 对T 的引用."

[expr]

5 如果表达式最初具有引用到T"([dcl.ref], [dcl.init.ref]),在任何之前将类型调整为T更深入的分析.表达式指定对象或函数由引用表示,并且表达式是左值或xvalue,取决于表达式.

[expr.prim.general]

8一个标识符 是一个 id 表达式,前提是它被适当地声明(条款[dcl.dcl]).表达式的类型是标识符.结果是标识符表示的实体.如果实体是函数、变量或数据,则结果是左值成员和 prvalue 否则.

[expr.call]

10 如果结果类型是左值引用类型或对函数类型的右值引用,如果是 xvalue结果类型是对对象类型的右值引用,和右值否则.

现在允许我们回答您的问题.

<块引用>

在第 1 行中,x 是命名函数参数的标识符(id 表达式).它的类型是 int&&,这是 decltype(x) 返回的类型.x 不是表达式,也没有值类别.

是的.声明中的 x 不是表达式.但是作为 decltype 的参数,是 一个表达式.但是,它遇到了 decltype 的第一个项目符号的特殊情况,因此推导出由 x 命名的标识符的类型,而不是 x作为表达式.

<块引用>

在第 2 行中,x 是一个表达式.类型调整前,其类型为int&&,类型调整后为int.值类别为左值.

是的.

<块引用>

在第 3 行中,std::move(x) 是一个表达式.其调整前的类型是int&&,后-int.值类别为 xvalue.

是的.

<块引用>

当我们说 x 具有右值引用类型时,我们要么将 x 的类型称为标识符,要么将 x 的类型称为code> 作为类型调整前的表达式.

是的.

<块引用>

cppreference.com 的每个表达式都有一些非引用类型,每个表达式都属于三个主要值类别之一"中的类型"一词是指类型调整后的类型.

是的.

<块引用>

当 Scott Meyers 写道如果表达式的类型是左值引用(例如,T& 或 const T& 等),则该表达式是一个左值."he指的是调整前的类型,第二个lvalue"指的是数值类别.

无法确定 Scott Meyers 在写这篇文章时的意思,但这是对符合标准的词语的唯一解释,是的.

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.

解决方案

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.

相关文章