值类别
每个 C++ 表达式(运算符及其操作数、字面量、变量名等)都由两个独立的属性描述:类型和值类别。每个表达式都有某种非引用类型,并且每个表达式都恰好属于三个主要值类别之一:prvalue、xvalue 和 lvalue。
- 计算内置运算符的操作数的值(此类 prvalue 没有结果对象),或
- 初始化一个对象(此类 prvalue 被称为具有结果对象)。
扩展内容 |
---|
历史上之所以如此称呼,是因为左值可以出现在赋值表达式的左侧。但通常情况并非总是如此 void foo(); void baz() { int a; // Expression `a` is lvalue a = 4; // OK, could appear on the left-hand side of an assignment expression int &b{a}; // Expression `b` is lvalue b = 5; // OK, could appear on the left-hand side of an assignment expression const int &c{a}; // Expression `c` is lvalue c = 6; // ill-formed, assignment of read-only reference // Expression `foo` is lvalue // address may be taken by built-in address-of operator void (*p)() = &foo; foo = baz; // ill-formed, assignment of function } |
- rvalue 是 prvalue 或 xvalue;
扩展内容 |
---|
历史上之所以如此称呼,是因为右值可以出现在赋值表达式的右侧。但通常情况并非总是如此 运行此代码 #include <iostream> struct S { S() : m{42} {} S(int a) : m{a} {} int m; }; int main() { S s; // Expression `S{}` is prvalue // May appear on the right-hand side of an assignment expression s = S{}; std::cout << s.m << '\n'; // Expression `S{}` is prvalue // Can be used on the left-hand side too std::cout << (S{} = S{7}).m << '\n'; } 输出 42 7 |
注意:此分类法在过去的 C++ 标准修订中经历了重大更改,请参阅下面的 历史 部分了解详情。
扩展内容 |
---|
尽管名称如此,但这些术语分类的是表达式,而不是值。 运行此代码 #include <type_traits> #include <utility> template <class T> struct is_prvalue : std::true_type {}; template <class T> struct is_prvalue<T&> : std::false_type {}; template <class T> struct is_prvalue<T&&> : std::false_type {}; template <class T> struct is_lvalue : std::false_type {}; template <class T> struct is_lvalue<T&> : std::true_type {}; template <class T> struct is_lvalue<T&&> : std::false_type {}; template <class T> struct is_xvalue : std::false_type {}; template <class T> struct is_xvalue<T&> : std::false_type {}; template <class T> struct is_xvalue<T&&> : std::true_type {}; int main() { int a{42}; int& b{a}; int&& r{std::move(a)}; // Expression `42` is prvalue static_assert(is_prvalue<decltype((42))>::value); // Expression `a` is lvalue static_assert(is_lvalue<decltype((a))>::value); // Expression `b` is lvalue static_assert(is_lvalue<decltype((b))>::value); // Expression `std::move(a)` is xvalue static_assert(is_xvalue<decltype((std::move(a)))>::value); // Type of variable `r` is rvalue reference static_assert(std::is_rvalue_reference<decltype(r)>::value); // Type of variable `b` is lvalue reference static_assert(std::is_lvalue_reference<decltype(b)>::value); // Expression `r` is lvalue static_assert(is_lvalue<decltype((r))>::value); } |
内容 |
[编辑] 主要类别
[编辑] 左值 (lvalue)
以下表达式是左值表达式
- 变量、函数、模板形参对象(自 C++20 起)或数据成员的名称,无论类型如何,例如 std::cin 或 std::endl。即使变量的类型是右值引用,由其名称组成的表达式也是左值表达式(但也请参阅可移动表达式);
扩展内容 |
---|
void foo() {} void baz() { // `foo` is lvalue // address may be taken by built-in address-of operator void (*p)() = &foo; } struct foo {}; template <foo a> void baz() { const foo* obj = &a; // `a` is an lvalue, template parameter object } |
- 函数调用或重载运算符表达式,其返回类型是左值引用,例如 std::getline(std::cin, str)、 std::cout << 1、 str1 = str2 或 ++it;
扩展内容 |
---|
int& a_ref() { static int a{3}; return a; } void foo() { a_ref() = 5; // `a_ref()` is lvalue, function call whose return type is lvalue reference } |
- a = b、 a += b、 a %= b 以及所有其他内置的 赋值和复合赋值 表达式;
- ++a 和 --a,内置的 前缀递增和前缀递减 表达式;
- *p,内置的 间接引用 表达式;
- a[n] 和 p[n],内置的 下标 表达式,其中 a[n] 中的一个操作数是数组左值(自 C++11 起);
- a.m,对象成员 表达式,除非
m
是成员枚举数或非静态成员函数,或者 a 是右值且m
是对象类型的非静态数据成员;
扩展内容 |
---|
struct foo { enum bar { m // member enumerator }; }; void baz() { foo a; a.m = 42; // ill-formed, lvalue required as left operand of assignment } struct foo { void m() {} // non-static member function }; void baz() { foo a; // `a.m` is a prvalue, hence the address cannot be taken by built-in // address-of operator void (foo::*p1)() = &a.m; // ill-formed void (foo::*p2)() = &foo::m; // OK: pointer to member function } struct foo { static void m() {} // static member function }; void baz() { foo a; void (*p1)() = &a.m; // `a.m` is an lvalue void (*p2)() = &foo::m; // the same } |
- p->m,内置的 指针成员 表达式,除非
m
是成员枚举数或非静态成员函数; - a.*mp,对象成员指针 表达式,其中 a 是左值且
mp
是指向数据成员的指针; - p->*mp,内置的 指针成员指针 表达式,其中
mp
是指向数据成员的指针; - a, b,内置的 逗号 表达式,其中 b 是左值;
- a ? b : c,对于某些 b 和 c 的 三元条件 表达式(例如,当两者都是相同类型的左值时,但请参阅 定义 了解详情);
- 字符串字面量,例如 "Hello, world!";
- 到左值引用类型的强制转换表达式,例如 static_cast<int&>(x) 或 static_cast<void(&)(int)>(x);
- 左值引用类型的非类型 模板形参;
template <int& v> void set() { v = 5; // template parameter is lvalue } int a{3}; // static variable, fixed address is known at compile-time void foo() { set<a>(); }
|
(自 C++11 起) |
属性
- 与 泛左值 (glvalue) 相同(见下文)。
- 可以使用内置的取地址运算符获取左值的地址:&++i[1] 和 &std::endl 是有效的表达式。
- 可修改的左值可以用作内置赋值运算符和复合赋值运算符的左侧操作数。
- 左值可用于初始化左值引用;这会将新名称与表达式标识的对象关联起来。
[编辑] 纯右值 (prvalue)
以下表达式是纯右值表达式
- 字面量(字符串字面量 除外),例如 42、 true 或 nullptr;
- 函数调用或重载运算符表达式,其返回类型是非引用,例如 str.substr(1, 2)、 str1 + str2 或 it++;
- a++ 和 a--,内置的 后缀递增和后缀递减 表达式;
- a + b、 a % b、 a & b、 a << b 以及所有其他内置的 算术 表达式;
- a && b、 a || b、 !a,内置的 逻辑 表达式;
- a < b、 a == b、 a >= b 以及所有其他内置的 比较 表达式;
- &a,内置的 取地址 表达式;
- a.m,对象成员 表达式,其中
m
是成员枚举数或非静态成员函数[2]; - p->m,内置的 指针成员 表达式,其中
m
是成员枚举数或非静态成员函数[2]; - a.*mp,对象成员指针 表达式,其中
mp
是指向成员函数的指针[2]; - p->*mp,内置的 指针成员指针 表达式,其中
mp
是指向成员函数的指针[2]; - a, b,内置的 逗号 表达式,其中 b 是 prvalue;
- a ? b : c,对于某些 b 和 c 的 三元条件 表达式(请参阅 定义 了解详情);
- 到非引用类型的强制转换表达式,例如 static_cast<double>(x)、 std::string{} 或 (int)42;
this
指针;- 枚举数;
- 标量类型的非类型 模板形参;
template <int v> void foo() { // not an lvalue, `v` is a template parameter of scalar type int const int* a = &v; // ill-formed v = 3; // ill-formed: lvalue required as left operand of assignment }
|
(自 C++11 起) |
|
(自 C++20 起) |
属性
- 与 右值 (rvalue) 相同(见下文)。
- prvalue 不能是多态的:它表示的对象的动态类型始终是表达式的类型。
- 非类非数组 prvalue 不能是 cv 限定的,除非它被具体化以便绑定到 cv 限定类型的引用(自 C++17 起)。(注意:函数调用或强制转换表达式可能会产生非类 cv 限定类型的 prvalue,但 cv 限定符通常会立即被剥离。)
- prvalue 不能具有不完整类型(类型 void 除外,请参见下文,或在
decltype
说明符中使用时)。 - prvalue 不能具有抽象类类型或其数组。
[编辑] 将亡值 (xvalue)
以下表达式是将亡值表达式
- a.m,对象成员 表达式,其中 a 是右值,且
m
是对象类型的非静态数据成员; - a.*mp,对象成员指针 表达式,其中 a 是右值,且
mp
是指向数据成员的指针; - a, b,内置的 逗号 表达式,其中 b 是 xvalue;
- a ? b : c,对于某些 b 和 c 的 三元条件 表达式(请参阅 定义 了解详情);
|
(自 C++11 起) |
|
(自 C++17 起) |
(自 C++23 起) |
属性
- 与右值 (rvalue) 相同(见下文)。
- 与泛左值 (glvalue) 相同(见下文)。
特别是,与所有右值一样,xvalue 绑定到右值引用;并且与所有泛左值一样,xvalue 可以是多态的,并且非类 xvalue 可以是 cv 限定的。
扩展内容 |
---|
运行此代码 #include <type_traits> template <class T> struct is_prvalue : std::true_type {}; template <class T> struct is_prvalue<T&> : std::false_type {}; template <class T> struct is_prvalue<T&&> : std::false_type {}; template <class T> struct is_lvalue : std::false_type {}; template <class T> struct is_lvalue<T&> : std::true_type {}; template <class T> struct is_lvalue<T&&> : std::false_type {}; template <class T> struct is_xvalue : std::false_type {}; template <class T> struct is_xvalue<T&> : std::false_type {}; template <class T> struct is_xvalue<T&&> : std::true_type {}; // Example from C++23 standard: 7.2.1 Value category [basic.lval] struct A { int m; }; A&& operator+(A, A); A&& f(); int main() { A a; A&& ar = static_cast<A&&>(a); // Function call with return type rvalue reference is xvalue static_assert(is_xvalue<decltype( (f()) )>::value); // Member of object expression, object is xvalue, `m` is a non-static data member static_assert(is_xvalue<decltype( (f().m) )>::value); // A cast expression to rvalue reference static_assert(is_xvalue<decltype( (static_cast<A&&>(a)) )>::value); // Operator expression, whose return type is rvalue reference to object static_assert(is_xvalue<decltype( (a + a) )>::value); // Expression `ar` is lvalue, `&ar` is valid static_assert(is_lvalue<decltype( (ar) )>::value); [[maybe_unused]] A* ap = &ar; } |
[编辑] 混合类别
[编辑] 泛左值 (glvalue)
泛左值表达式是左值或将亡值。
属性
[编辑] 右值 (rvalue)
右值表达式是纯右值或将亡值。
属性
- 无法使用内置的取地址运算符获取右值的地址:&int()、 &i++[3]、 &42 和 &std::move(x) 无效。
- 右值不能用作内置赋值运算符或复合赋值运算符的左侧操作数。
- 右值可用于初始化常量左值引用,在这种情况下,右值标识的临时对象的生命周期会延长,直到引用的作用域结束。
(自 C++11 起) |
[编辑] 特殊类别
[编辑] 待定的成员函数调用
表达式 a.mf 和 p->mf,其中 mf
是一个 非静态成员函数,以及表达式 a.*pmf 和 p->*pmf,其中 pmf
是一个 指向成员函数的指针,被归类为纯右值表达式,但它们不能用于初始化引用,作为函数参数,或用于任何目的,除非作为函数调用运算符的左侧参数,例如 (p->*pmf)(args)。
[编辑] void 表达式
返回 void 的函数调用表达式,到 void 的强制类型转换表达式,以及 throw 表达式 被归类为纯右值表达式,但它们不能用于初始化引用或作为函数参数。它们可以用于丢弃值上下文(例如,在单独一行上,作为逗号运算符的左侧操作数等)以及返回 void 的函数中的 return 语句中。此外,throw 表达式可以用作条件运算符 ?: 的第二个和第三个操作数。
Void 表达式没有结果对象。 |
(自 C++17 起) |
[编辑] 位域
指定位域的表达式(例如 a.m,其中 a 是 struct A { int m: 3; } 类型的左值)是一个泛左值表达式:它可以用作赋值运算符的左侧操作数,但不能获取其地址,并且非常量左值引用不能绑定到它。常量左值引用或右值引用可以从位域泛左值初始化,但会创建位域的临时副本:它不会直接绑定到位域。
可移动表达式尽管由任何变量名称组成的表达式都是左值表达式,但如果它作为以下内容的操作数出现,则此类表达式可能是可移动的 如果一个表达式是可移动的,则为了重载解析的目的,它被视为作为右值或作为左值(直到 C++23)作为右值(自 C++23 起)(因此它可能会选择移动构造函数)。有关详细信息,请参阅从局部变量和参数自动移动。 |
(自 C++11 起) |
[编辑] 历史
[编辑] CPL
编程语言 CPL 最先引入了表达式的值类别:所有 CPL 表达式都可以在“右手模式”下求值,但只有某些类型的表达式在“左手模式”下才有意义。当在右手模式下求值时,表达式被视为计算值的规则(右手值,或右值)。当在左手模式下求值时,表达式有效地给出一个地址(左手值,或左值)。“左”和“右”在这里代表“赋值的左侧”和“赋值的右侧”。
[编辑] C
C 编程语言遵循类似的分类法,只是赋值的作用不再重要:C 表达式分为“左值表达式”和其他表达式(函数和非对象值),其中“左值”表示标识对象的表达式,即“定位器值”[4]。
[编辑] C++98
2011 年之前的 C++ 遵循 C 模型,但恢复了“右值”名称来表示非左值表达式,使函数成为左值,并添加了引用可以绑定到左值的规则,但只有对 const 的引用可以绑定到右值。几个非左值 C 表达式在 C++ 中变成了左值表达式。
[编辑] C++11
随着 C++11 中移动语义的引入,值类别被重新定义,以表征表达式的两个独立属性[5]
- 具有标识:可以确定表达式是否与另一个表达式引用相同的实体,例如通过比较它们标识的对象或函数的地址(直接或间接获得);
- 可以从中移动:移动构造函数、移动赋值运算符 或实现移动语义的另一个函数重载可以绑定到该表达式。
在 C++11 中,表达式
- 具有标识且无法从中移动的称为左值表达式;
- 具有标识且可以从中移动的称为将亡值表达式;
- 不具有标识且可以从中移动的称为纯右值(“pure rvalue”)表达式;
- 不具有标识且无法从中移动的未使用[6]。
具有标识的表达式称为“泛左值表达式”(glvalue 代表“generalized lvalue”)。左值和将亡值都是泛左值表达式。
可以从中移动的表达式称为“右值表达式”。纯右值和将亡值都是右值表达式。
[编辑] C++17
在 C++17 中,复制省略在某些情况下成为强制性的,这需要将纯右值表达式与由它们初始化的临时对象分离,从而产生了我们今天的系统。请注意,与 C++11 方案相比,纯右值不再从中移动。
[编辑] 脚注
- ↑ 假设 i 具有内置类型或前自增运算符被重载为按左值引用返回。
- ↑ 2.0 2.1 2.2 2.3 特殊右值类别,请参阅待定的成员函数调用。
- ↑ 假设 i 具有内置类型或后自增运算符未被重载为按左值引用返回。
- ↑ “C 社区内关于左值的含义存在意见分歧,一组人认为左值是任何类型的对象定位器,另一组人认为左值在赋值运算符的左侧才有意义。C89 委员会采纳了左值作为对象定位器的定义。”——ANSI C Rationale,6.3.2.1/10。
- ↑ “新”值术语,Bjarne Stroustrup,2010 年。
- ↑ const 纯右值(仅允许用于类类型)和 const 将亡值不绑定到
T&&
重载,但它们绑定到 const T&& 重载,标准也将它们归类为“移动构造函数”和“移动赋值运算符”,满足此分类目的的“可以从中移动”的定义。但是,此类重载无法修改其参数,并且在实践中不使用;在它们不存在的情况下,const 纯右值和 const 将亡值绑定到 const T& 重载。
[编辑] 参考文献
- C++23 标准 (ISO/IEC 14882:2024)
- 7.2.1 值类别 [basic.lval]
- C++20 标准 (ISO/IEC 14882:2020)
- 7.2.1 值类别 [basic.lval]
- C++17 标准 (ISO/IEC 14882:2017)
- 6.10 左值和右值 [basic.lval]
- C++14 标准 (ISO/IEC 14882:2014)
- 3.10 左值和右值 [basic.lval]
- C++11 标准 (ISO/IEC 14882:2011)
- 3.10 左值和右值 [basic.lval]
- C++98 标准 (ISO/IEC 14882:1998)
- 3.10 左值和右值 [basic.lval]
[编辑] 缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
CWG 616 | C++11 | 成员访问和通过成员访问 右值的成员指针导致纯右值 |
重新分类为将亡值 |
CWG 1059 | C++11 | 数组纯右值不能是 cv 限定的 | 允许 |
CWG 1213 | C++11 | 下标数组右值导致左值 | 重新分类为将亡值 |
[编辑] 另请参阅
C 文档,关于 值类别
|
[编辑] 外部链接
1. | C++ 值类别和 decltype 解惑 — David Mazières, 2021 | |
2. | 经验性地确定表达式的值类别 — StackOverflow |