x
本身就是一个左值。
以下表达式是左值表达式:
- 变量、函数
, a template parameter object (since C++20)
或数据成员的名称,与类型无关,例如 std::cin 或 std::endl。即使变量的类型是右值引用,由其名称组成的表达式也是左值表达式;
对于局部变量 as x
,在return 语句和throw 表达式中,初始化的重载解析分两阶段进行;首先好像x
是一个右值表达式(然后可能会选择移动构造函数)。
在回报声明:
or, for co_return, to select the overload of promise.return_value() (since C++20)
(自 C++11 起)然后执行两次重载决议以选择用于初始化返回值的构造函数:
- 首先好像表达式是一个右值表达式(因此它可以选择移动构造函数),并且
在抛出表达式中:
- 这也可以调用左值表达式的移动构造函数,如果它们命名局部变量或函数或 catch 子句参数,其范围不超过最内层的封闭 try 块(如果有),通过与 return 语句中相同的两步重载决议(C++17 起)
作为效果,在这两种情况下都选择了移动构造函数。这只是特殊的return
and throw
,并不意味着x
isrvalue
或xvalue
完全。如果你像X x2(x);
in一样写 sth f()
,将选择复制构造函数(并导致错误,因为复制构造函数被隐式删除)。
根据标准,[class.copy.elision]/3:
隐式可移动实体是自动存储持续时间的变量,它可以是非易失性对象,也可以是对非易失性对象类型的右值引用。在以下复制初始化上下文中,在尝试复制操作之前首先考虑移动操作:
(3.1) - 如果 return ([stmt.return]) 或 co_return ([stmt.return.coroutine]) 语句中的表达式是一个(可能带括号的)id 表达式,它命名在主体或参数中声明的隐式可移动实体- 最内层封闭函数或 lambda 表达式的声明子句,或
(3.2) - 如果 throw 表达式 ([expr.throw]) 的操作数是一个(可能带括号的)id 表达式,它命名一个隐式可移动实体,该实体属于不包含最里面的复合语句的范围try-block 或 function-try-block(如果有),其复合语句或 ctor-initializer 包含 throw 表达式,
首先执行为复制选择构造函数的重载决策或要调用的 return_value 重载,就好像表达式或操作数是右值一样。如果第一次重载决议失败或未执行,则再次执行重载决议,将表达式或操作数视为左值。
[expr.prim.id.unqual]/3:
如果实体是函数、变量、结构化绑定、数据成员或模板参数对象,则表达式为左值,否则为纯右值 ([basic.lval]);
[基本.lval]:
(1.1) - 泛左值是一个表达式,其评估确定对象或函数的身份。
(1.2) - 纯右值是一个表达式,它的求值初始化一个对象或计算一个运算符的操作数的值,由它出现的上下文指定,或者是一个类型为 cv void 的表达式。
(1.3) - 一个xvalue是一个glvalue,它表示一个对象的资源可以被重用(通常是因为它接近其生命周期的尽头)。
(1.4) - 左值是不是 xvalue 的左值。
(1.5) - rvalue 是 prvalue 或 xvalue。
[注 3:表达式是一个 xvalue,如果它是:
(4.1) - 调用函数的结果,无论是隐式还是显式,其返回类型是对对象类型的右值引用 ([expr.call]),
(4.2) - 对对象类型的右值引用的强制转换 ([expr.type.conv], [expr.dynamic.cast], [expr.static.cast] [expr.reinterpret.cast], [expr.const.演员], [expr.cast]),
(4.3) - 具有 xvalue 数组操作数 ([expr.sub]) 的下标操作,
(4.4) - 类成员访问表达式,指定非引用类型的非静态数据成员,其中对象表达式是 xvalue ([expr.ref]),或
(4.5) - 一个 .* 指向成员的表达式,其中第一个操作数是一个 xvalue,第二个操作数是一个指向数据成员的指针 ([expr.mptr.oper])。