跳转至

C++ 标准中值类别的中文翻译

以下内容是《ISO/IEC 14882:2024》[7.2.1] Value category [basic.lval] 部分的中文翻译。标准文件可从此仓库下载。


          expression         
           /     \           
          /       \          
     glvalue      rvalue     
      /    \      /   \      
     /      \    /     \     
 lvalue     xvalue    prvalue
  • glvalue(泛左值)是一个表达式,其求值结果决定了某个对象或函数的身份(identity)。
  • prvalue(纯右值)是一个表达式,其求值用于初始化一个对象、或在特定上下文中计算某个运算符的操作数,或者是一个类型为 cv void 的表达式。
  • xvalue(亡值)是一个 glvalue,它表示一个其资源可以被重用的对象(通常是因为该对象即将结束其生命周期)。
  • lvalue(左值)是一个不是 xvalue 的 glvalue。
  • rvalue(右值)是一个 prvalue 或者 xvalue。

每一条表达式恰好属于本分类体系中以下三种基本类别之一:lvalue、xvalue 或 prvalue。这种表达式的属性被称为它的值类别(value category)。

注 1:[7.6] 节中对每一个内建运算符的讨论,都指明了该运算符所产生的结果的值类别,以及它所期望的操作数的值类别。例如,内建的赋值运算符要求其左侧操作数为 lvalue,右侧操作数为 prvalue,并产生一个 lvalue 类型的结果。用户定义的运算符本质上是函数,其所接受的操作数和所产生的结果的值类别由其参数类型与返回类型决定。

注 2:历史上,lvalue 和 rvalue 这两个术语来源于它们通常出现在赋值表达式的左边或右边(尽管这一规则在现代语言规范中已不再普遍适用);glvalue 是 “广义的” lvalue,prvalue 是 “纯正的” rvalue,而 xvalue 是 “将亡的” lvalue。尽管这些术语中包含 “值(value)”,但它们实际上是对表达式的分类,而非对实际数据值的描述。

注 3:一个表达式是 xvalue(将亡值),如果它满足以下任一条件:

  • 它是一个可移动的合格 id 表达式(id-expression)(参见 [7.5.5.2] 节);
  • 它是调用一个返回类型为对象类型的右值引用(rvalue reference)的函数所得到的结果,无论该函数调用是显式的还是隐式的(参见 [7.6.1.3] 节);
  • 它是一个到对象类型的右值引用的显式类型转换(cast)的结果(参见 [7.6.1.4][7.6.1.7][7.6.1.9][7.6.1.10][7.6.1.11][7.6.3] 节);
  • 它是一个使用 xvalue 类型数组操作数进行下标访问(subscripting)所得的结果(参见 [7.6.1.2] 节);
  • 它是一个类成员访问表达式,用于指代一个非静态数据成员,且该成员的类型不是引用类型,同时其对象表达式(object expression)本身是一个 xvalue(参见 [7.6.1.5] 节);
  • 它是一个指向成员的 .* 操作符表达式,其中第一个操作数是 xvalue,第二个操作数是指向数据成员的指针(pointer to data member)(参见 [7.6.4] 节)。

一般而言,此规则的效果是:命名的右值引用(named rvalue references)被视为左值(lvalues),而未命名的右值引用到对象(unnamed rvalue references to objects)被视为将亡值(xvalues);对于右值引用到函数(rvalue references to functions),无论是否命名,均被视为左值(lvalues)。

举例:

struct A
{
    int m;
};
A &&operator+(A, A);
A &&f();
A a;
A &&ar = static_cast<A &&>(a);

表达式 f()f().mstatic_cast<A&&>(a)a + a 是 xvalue。表达式 ar 是一个 lvalue。

一个 glvalue(“广义左值”)的结果是该表达式所表示的实体(entity)。一个 prvalue(“纯右值”)的结果是该表达式在其求值上下文中所存储的值;类型为 cv void 的 prvalue 没有结果。一个结果为值 V 的 prvalue 有时被称为 “拥有” 或 “命名” 该值 V。prvalue 的结果对象(result object)是指由该 prvalue 初始化的对象;如果某个非被丢弃的 prvalue 被用于计算某个内建运算符的操作数,或者其类型为 cv void,则它没有结果对象。

注 4:除非该纯右值(prvalue)是 decltype-specifier 的操作数,否则类型为类类型或数组类型的纯右值总是具有一个结果对象(result object)。对于类型不是 cv void 的被丢弃纯右值(discarded prvalue),会物化一个临时对象(temporary object);参见 [7.2.3]

注 5:试图将右值引用绑定到左值(lvalue)的情形不属于此类上下文;参见 [9.4.4]

注 6:因为当非类类型的表达式被转换为纯右值时,其类型中的 cv 限定符会被移除,例如,类型为 const int 的左值可以在需要类型为 int 的纯右值的上下文中使用。

注 7:不存在作为纯右值的位域(bit-field);如果一个位域被转换为纯右值(参见 [7.3.2]),则会创建一个其位域类型的纯右值,该纯右值随后可能进行整数提升(integer promotion)(参见 [7.3.7])。

每当一个纯右值(prvalue)作为某个操作符的操作数出现,而该操作符期望的是一个广义左值(glvalue)时,则会应用临时对象物化转换(见 [7.3.5])将该表达式转换为一个亡值(xvalue)。

在第 [9.4.4] 节关于引用初始化的讨论以及第 [6.7.7] 节关于临时对象的内容中,描述了左值和右值在其他重要上下文中的行为。

除非另有说明(见 [9.2.9.5]),一个纯右值的类型应当始终是完整类型(complete type)或 void 类型;如果其类型是一个类类型,或者是一个(可能是多维的)类类型的数组,则该类不得为抽象类(见 [11.7.4])。广义左值(glvalue)不得具有 cv 限定的 void 类型。

注 8:广义左值可以具有完整的或不完整的非 void 类型。类类型和数组类型的纯右值可以具有 cv 限定类型;其他类型的纯右值总是具有非 cv 限定类型。参见 [7.2.2]

一个左值是可修改的,除非其类型是 const 限定的,或者是一个函数类型。

注 9:若程序试图通过一个不可修改的左值或通过一个右值来修改一个对象,则该程序格式错误(ill-formed)(见 [7.6.19][7.6.1.6][7.6.2.3])。

若一程序试图通过一个泛左值(glvalue)访问某一对象的存储值,而该泛左值的类型与下列类型之一不相似(not similar,见 [7.3.6]),则行为未定义(undefined behavior):

  • 该对象的动态类型(dynamic type);
  • 与该对象的动态类型相对应的有符号或无符号类型;
  • char、unsigned char 或 std::byte 类型。

若一程序对某一联合体(union)类型 U 的默认生成的拷贝 / 移动构造函数或拷贝 / 移动赋值运算符进行调用时,使用了一个泛左值实参,而该实参并不表示在其生存期内的一个 cv U 类型的对象,则行为未定义。

注 10:在 C 语言中,可以通过例如赋值操作来访问整个结构体类型的对象。相比之下,C++ 中并不存在通过类类型的左值来访问类类型对象的概念。