命名空间
变体
操作

值类别

来自 cppreference.cn
< cpp‎ | 语言
 
 
C++ 语言
表达式
值类别
求值顺序
替代表示
字面量
布尔字面量 - 整数字面量 - 浮点字面量
字符字面量 - 字符串字面量 - nullptr (C++11)
用户定义 (C++11)
工具
属性 (C++11)
类型
typedef 声明
类型别名声明 (C++11)
类型转换
内存分配
类特有的函数属性
explicit (C++11)
static

特殊成员函数
模板
杂项
 
 

每个 C++ 表达式(运算符及其操作数、字面量、变量名等)都由两个独立的属性来描述:一个类型和一个值类别。每个表达式都有一个非引用类型,并且每个表达式都精确地属于三种主要值类别之一:prvaluexvaluelvalue

  • 一个 glvalue(“广义”左值)是一个表达式,其求值确定了一个对象或函数的身份;
  • 一个 prvalue(“纯”右值)是一个表达式,其求值
  • 计算内置运算符操作数的值(这种 prvalue 没有结果对象),或者
  • 初始化一个对象(这种 prvalue 被认为有一个结果对象)。
结果对象可以是一个变量,一个由 new-expression 创建的对象,一个由 临时实质化 创建的临时对象,或者它们的成员。请注意,非 void 被丢弃的 表达式具有结果对象(实质化的临时对象)。此外,每个类和数组 prvalue 都有一个结果对象,除非它是 decltype 的操作数;
  • 一个 xvalue(“将过期”的值)是一个 glvalue,它表示一个其资源可以被重用的对象;
  • 一个 lvalue 是一个不是 xvalue 的 glvalue;
扩展内容

历史地,被称为左值,因为左值可以出现在赋值表达式的左侧。通常情况并非总是如此

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

以下表达式是lvalue 表达式

扩展内容
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 << 1str1 = 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 = ba += ba %= 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,某些 bc三元条件表达式(例如,当两者都是相同类型的左值时,但请参阅定义了解详情);
  • 字符串字面量,例如 "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>();
}
  • 函数调用或重载运算符表达式,其返回类型为函数右值引用;
  • 转换为函数右值引用类型的转换表达式,例如 static_cast<void(&&)(int)>(x)
(C++11 起)

属性

  • glvalue(下述)。
  • 可以通过内置的取地址运算符获取左值的地址:&++i[1]&std::endl 是有效表达式。
  • 可修改的左值可以用作内置赋值和复合赋值运算符的左操作数。
  • 左值可用于初始化左值引用;这为表达式标识的对象关联一个新名称。

[编辑] prvalue

以下表达式是 prvalue 表达式

  • 字面量字符串字面量除外),例如 42truenullptr
  • 函数调用或重载运算符表达式,其返回类型为非引用,例如 str.substr(1, 2)str1 + str2it++
  • a++a--,内置的后置增量和后置减量表达式;
  • a + ba % ba & ba << b 以及所有其他内置算术表达式;
  • a && ba || b!a,内置的逻辑表达式;
  • a < ba == ba >= 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,某些 bc三元条件表达式(请参阅定义了解详情);
  • 转换为非引用类型的转换表达式,例如 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 起)

属性

[编辑] xvalue

以下表达式是 xvalue 表达式

  • a.m对象的成员表达式,其中 a 是右值,m 是对象类型的非静态数据成员;
  • a.*mp指向对象成员的指针表达式,其中 a 是右值,mp 是指向数据成员的指针;
  • a, b,内置的逗号表达式,其中 b 是 xvalue;
  • a ? b : c,某些 bc三元条件表达式(请参阅定义了解详情);
  • 函数调用或重载运算符表达式,其返回类型为对象右值引用,例如 std::move(x)
  • a[n],内置的下标表达式,其中一个操作数是数组右值;
  • 转换为对象右值引用类型的转换表达式,例如 static_cast<char&&>(x)
(C++11 起)
(C++17 起)
(C++23 起)

属性

  • 同 rvalue(下述)。
  • 同 glvalue(下述)。

特别地,像所有右值一样,xvalue 绑定到右值引用,并且像所有 glvalue 一样,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

一个 glvalue 表达式 既可以是 lvalue 也可以是 xvalue。

属性

  • glvalue 可以通过左值到右值、数组到指针或函数到指针的隐式转换隐式转换为 prvalue。
  • glvalue 可以是多态的:它所标识的对象的动态类型不一定是表达式的静态类型。
  • glvalue 可以具有不完整类型,只要表达式允许。

[编辑] rvalue

一个 rvalue 表达式 既可以是 prvalue 也可以是 xvalue。

属性

  • 右值的地址不能通过内置的取地址运算符获取:&int()&i++[3]&42&std::move(x) 是无效的。
  • 右值不能用作内置赋值或复合赋值运算符的左操作数。
  • 右值可用于初始化 const 左值引用,在这种情况下,右值标识的临时对象的生命周期将延长至引用作用域结束。
  • 右值可用于初始化右值引用,在这种情况下,右值标识的临时对象的生命周期将延长至引用作用域结束。
  • 当用作函数参数时,并且函数有两个重载可用,一个接受右值引用参数,另一个接受 const 左值引用参数时,右值会绑定到右值引用重载(因此,如果复制构造函数和移动构造函数都可用,右值参数会调用移动构造函数,赋值运算符也是如此)。
(C++11 起)

[编辑] 特殊类别

[编辑] 待决成员函数调用

表达式 a.mfp->mf,其中 mf非静态成员函数,以及表达式 a.*pmfp->*pmf,其中 pmf指向成员函数的指针,都被归类为 prvalue 表达式,但它们不能用于初始化引用、作为函数参数,或用于任何目的,除非作为函数调用运算符的左操作数,例如 (p->*pmf)(args)

[编辑] Void 表达式

返回 void 的函数调用表达式,转换为 void 的转换表达式,以及 throw-expression 都被归类为 prvalue 表达式,但它们不能用于初始化引用或作为函数参数。它们可以用于被丢弃值的上下文(例如,单独一行,作为逗号运算符的左操作数等)以及返回 void 的函数中的 return 语句。此外,throw-expression 可以用作条件运算符 ?: 的第二个和第三个操作数。

void 表达式没有结果对象

(C++17 起)

[编辑] 位域

指定位域的表达式(例如 a.m,其中 a 是类型 struct A { int m: 3; } 的左值)是一个 glvalue 表达式:它可以作为赋值运算符的左操作数,但不能获取其地址,并且不能将非 const 左值引用绑定到它。const 左值引用或右值引用可以从位域 glvalue 初始化,但会创建一个位域的临时副本:它不会直接绑定到位域。

可移动表达式

虽然由任何变量名称组成的表达式是左值表达式,但如果它出现在以下操作数中,则该表达式可能是可移动的

如果一个表达式是可移动的,那么为了重载决议的目的,它既被视为右值又被视为左值(C++23 前)被视为右值(C++23 起)(因此它可能选择移动构造函数)。详见局部变量和参数的自动移动

(C++11 起)

[编辑] 历史

[编辑] CPL

编程语言 CPL 首先引入了表达式的值类别:所有 CPL 表达式都可以以“右值模式”求值,但只有某些类型的表达式在“左值模式”下有意义。当以右值模式求值时,表达式被视为计算值的规则(右侧值,或 rvalue)。当以左值模式求值时,表达式实际上给出一个地址(左侧值,或 lvalue)。这里的“左”和“右”代表“赋值的左侧”和“赋值的右侧”。

[编辑] C

C 编程语言采用了类似的分类法,只是赋值的作用不再重要:C 表达式分为“左值表达式”和其他(函数和非对象值),其中“左值”表示标识对象的表达式,“定位器值”[4]

[编辑] C++98

2011 年之前的 C++ 遵循 C 模型,但将“rvalue”这个名称恢复给非左值表达式,将函数变为左值,并添加了规则:引用可以绑定到左值,但只有 const 引用可以绑定到右值。一些非左值 C 表达式在 C++ 中成为左值表达式。

[编辑] C++11

随着 C++11 中移动语义的引入,值类别被重新定义以描述表达式的两个独立属性[5]

  • 具有身份:可以通过比较对象或函数(直接或间接获得)的地址来确定表达式是否引用与另一个表达式相同的实体;
  • 可以从中移动移动构造函数移动赋值运算符或其他实现移动语义的函数重载可以绑定到该表达式。

在 C++11 中,那些

  • 有身份且不能移动的表达式称为左值(lvalue)表达式;
  • 有身份且可以移动的表达式称为将亡值(xvalue)表达式;
  • 没有身份但可以移动的表达式称为纯右值(prvalue)表达式;
  • 没有身份且不能移动的表达式不被使用[6]

具有身份的表达式称为“glvalue 表达式”(glvalue 代表“广义左值”)。左值和将亡值都是 glvalue 表达式。

可以从中移动的表达式称为“rvalue 表达式”。纯右值和将亡值都是 rvalue 表达式。

[编辑] C++17

在 C++17 中,在某些情况下拷贝消除被强制执行,这要求将 prvalue 表达式与其初始化的临时对象分离,从而形成了我们今天的系统。请注意,与 C++11 的方案相反,prvalue 不再从中移动。

[编辑] 脚注

  1. 假设 i 具有内置类型或前置增量运算符重载为返回左值引用。
  2. 2.0 2.1 2.2 2.3 特殊右值类别,参见待决成员函数调用
  3. 假设 i 具有内置类型或者后置增量运算符未重载为返回左值引用。
  4. “C 社区内部对于左值(lvalue)的含义存在分歧,一方认为左值是任何形式的对象定位符,另一方则认为左值在赋值运算符的左侧有意义。C89 委员会采纳了将左值定义为对象定位符的定义。”—— ANSI C 原理,6.3.2.1/10。
  5. Bjarne Stroustrup 的 “新”值术语,2010 年。
  6. const prvalues(仅允许用于类类型)和 const xvalues 不绑定到 T&& 重载,但它们绑定到 const T&& 重载,这些重载也被标准归类为“移动构造函数”和“移动赋值运算符”,满足此分类目的的“可移动”定义。然而,此类重载不能修改其参数,在实践中不使用;在没有它们的情况下,const prvalues 和 const xvalues 绑定到 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++ 标准。

缺陷报告 应用于 发布时的行为 正确的行为
CWG 616 C++11 通过成员访问和成员访问
右值指向成员的结果是纯右值
重新分类为将亡值
CWG 1059 C++11 数组纯右值不能被 cv 限定 允许
CWG 1213 C++11 对数组右值进行下标操作导致左值 重新分类为将亡值

[编辑] 另请参见

C 文档 关于 值类别

[编辑] 外部链接

1.  C++ 值类别和 decltype 揭秘 — David Mazières, 2021
2.  经验性地确定表达式的值类别 — StackOverflow