右值、左值和形式定义
人们在听到这句话时会感到困惑
int&&X
x
具有右值引用类型,但 x
是左值.误解源于这样一个事实,即标识符和表达式是不同的东西,类型和值类别也是如此.此外,表达式的类型是在进一步分析之前进行调整",rvalue"和lvalue"这两个词可以出现在类型名称和值类别名称中.
我想澄清正式定义.假设我们有一个函数:
1 |void f(int&& x) {2 |... = x;3 |... = std::move(x);4 |}
下列说法正确吗?
- 在第 1 行中,
x
是命名函数参数的标识符(id 表达式).它的类型是int&&
,这是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"指的是值类别.
先说一些初步的段落:
<块引用>[基本]
3 实体是值、对象、引用、函数,枚举器,类型、类成员、模板、模板特化、命名空间、参数包,或者这个.
[dcl.type.simple]
4 decltype(e)
定义如下:
如果
e
是一个没有括号的 id-expression 或者一个没有括号的类成员访问 ([expr.ref]),decltype(e)
是类型由e
命名的实体.如果没有这样的实体,或者如果 e 命名了一个集合重载函数,程序格式错误;否则,如果
e
是 xvalue,decltype(e)
是T&&
,其中T
是e
的类型;否则,如果 e 是左值,则
decltype(e)
是T&
,其中T
是T
的类型代码>e;否则,
decltype(e)
就是e
的类型.
[dcl.ref]
1 在声明 TD
其中 D
有两种形式
并且声明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?
- In the line 1,
x
is an identifier (id-expression) that names a function parameter. Its type isint&&
, and this is the type thatdecltype(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 isint&&
, and after the type becomesint
. The value category is lvalue. - In the line 3,
std::move(x)
is an expression. Its type before adjustment isint&&
, after -int
. The value category is xvalue. - When we say that
x
has rvalue reference type, we refer either to the type ofx
as an identifier, or to the type ofx
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&
orconst 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 bye
. 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)
isT&&
, whereT
is the type ofe
;otherwise, if e is an lvalue,
decltype(e)
isT&
, whereT
is the type ofe
;otherwise,
decltype(e)
is the type ofe
.[dcl.ref]
1 In a declaration
T D
whereD
has either of the forms& attribute-specifier-seqopt D1 && attribute-specifier-seqopt D1and the type of the identifier in the declaration
T D1
is "derived-declarator-type-listT
," then the type of the identifier ofD
is "derived-declarator-type-list reference toT
."[expr]
5 If an expression initially has the type "reference to
T
" ([dcl.ref], [dcl.init.ref]), the type is adjusted toT
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 isint&&
, and this is the type thatdecltype(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 isint&&
, and after the type becomesint
. The value category is lvalue.
Yes.
In the line 3,
std::move(x)
is an expression. Its type before adjustment isint&&
, after -int
. The value category is xvalue.
Yes.
When we say that
x
has rvalue reference type, we refer either to the type ofx
as an identifier, or to the type ofx
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 constT&
, 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.
相关文章