学习总结
测试环境:使用GCC 13.2.0 和MSVC
1938(14.38.33130) ,基于C++20 。若未显式说明,则表示代码在GCC和MSVC中均可编译(静态断言均成功)。
概述
随着移动语义引入到C++11之中,C++的值类别 被重新定义以区别表达式的两种独立的性质:
拥有身份
(identity):可以确定表达式是否与另一表达式指代同一实体,例如通过比较它们所标识的对象或函数的(直接或间接获得的)地址。
可被移动 :移动构造函数、移动赋值运算符或实现了移动语义的其他函数重载能够绑定于这个表达式。
表达式是运算符 和它们的操作数 的序列,其指定一项计算。值类别是表达式的性质 。
例如,类型为右值引用的变量的名字构成的表达式是左值表达式。
1 2 int a{5 };int && b{std::move (a)};
拥有身份的表达式被称作“泛左值 (glvalue)
表达式”。左值和亡值都是泛左值表达式。
可被移动的表达式被称作“右值 (rvalue)
表达式”。纯右值和亡值都是右值表达式。
拥有身份且不可被移动的表达式被称作“左值
(lvalue) 表达式”。
拥有身份且可被移动的表达式被称作“亡值
(xvalue) 表达式”。
不拥有身份且可被移动的表达式被称作“纯右值
(prvalue) 表达式”。
不拥有身份且不可被移动的表达式无法使用,Bjarne
Stroustrup(C++创始人)认为这种值is not useful in C++ (or, I think)
in any other
language 。
l-left,r-right,p-pure,x-expiring,g-generalized。
判断值类别时使用的模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <type_traits> 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 {};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 > constexpr bool is_lvalue_v = is_lvalue<T>::value;template <class T > constexpr bool is_xvalue_v = is_xvalue<T>::value;template <class T > constexpr bool is_prvalue_v = is_prvalue<T>::value;
观察可知,将求值类型是左值引用 的表达式判断为左值表达式 ;将求值类型是右值引用 的表达式判断为亡值表达式 ;将求值类型是非引用 的表达式判断为纯右值表达式 。
使用上述模板时,应配合使用decltype((expr))
而不是decltype(expr)
来正确判断表达式的性质(两者可能相同也可能不同)。例如,当expr是单变量时,只有把变量的名字用一个或更多个()
包围时,编译器才会把操作数作为表达式求值:
1 2 3 4 5 6 7 8 int i = 1 ;decltype (i) a; static_assert (is_prvalue_v<decltype (i)>); static_assert (is_lvalue_v<decltype ((i))>);
If we wrap the variable’s name in one or more sets of parentheses,
the compiler will evaluate the operand as an expression. A variable is
an expression that can be the left-hand side of an assignment. As a
result, decltype on such an expression yields a reference.
——C++ primer 5th edition,2.5 Dealing with types,decltype and
reference
左值情形
左值情形1
变量、函数、数据成员、模板形参对象(C++20起)的名字。即使变量的类型是右值引用,由它的名字构成的表达式仍是左值表达式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void bar () {}struct foo { int l = 0 ; }x;template <foo a>void run () { static_assert (is_lvalue_v<decltype ((a))>); }int main () { int i = 0 ; static_assert (is_lvalue_v<decltype ((i))>); static_assert (is_lvalue_v<decltype ((bar))>); static_assert (is_lvalue_v<decltype ((x.l))>); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <utility> struct foo { int m = 0 ; }; foo&& operator +(foo, foo);int main () { static_assert (is_prvalue_v<decltype ((1 + 2 ))>); foo a1; static_assert (is_xvalue_v<decltype ((a1 + a1))>); static_assert (is_xvalue_v<decltype ((std::move (a1)))>); foo&& a2 = std::move (a1); static_assert (is_lvalue_v<decltype ((a2))>); return 0 ; }
左值情形2
相关:左值情形16 、纯右值情形2 、亡值情形5
返回类型是左值引用的函数调用或重载运算符表达式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> decltype (auto ) dog () { int x = 0 ; return x; }decltype (auto ) cat () { int x = 0 ; return (x); }int main () { static_assert (is_prvalue_v<decltype ((dog ()))>); static_assert (is_lvalue_v<decltype ((cat ()))>); static_assert (is_lvalue_v<decltype ((std::cout << 1 ))>); return 0 ; }
左值情形3
内建的赋值及复合赋值表达式。
1 2 3 4 int a = 0 , b = 1 ;static_assert (is_lvalue_v<decltype ((a = b))>);static_assert (is_lvalue_v<decltype ((a += b))>);static_assert (is_lvalue_v<decltype ((a %= b))>);
左值情形4
内建的前置自增与前置自减表达式。
1 2 3 int a = 0 ;static_assert (is_lvalue_v<decltype ((++a))>);static_assert (is_lvalue_v<decltype ((--a))>);
左值情形5
内建的间接寻址表达式。
1 2 3 int a = 0 ;int *p = &a;static_assert (is_lvalue_v<decltype ((*p))>);
左值情形6
内建的下标表达式a[n]
或n[a]
,除了a[n]
中的操作数不为数组左值时(C++11起)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int array[] = {0 , 1 , 2 };static_assert (is_lvalue_v<decltype ((array[0 ]))>);static_assert (is_lvalue_v<decltype ((0 [array]))>);static_assert (is_prvalue_v<decltype (((int [3 ]){1 , 2 , 3 }))>); using prvalue_array = int [3 ];static_assert (is_prvalue_v<decltype ((prvalue_array{1 , 2 , 3 }))>);typedef int prvalue_array[3 ];static_assert (is_prvalue_v<decltype ((prvalue_array{1 , 2 , 3 }))>);static_assert (is_prvalue_v<decltype ((std::type_identity_t <int [3 ]>{1 , 2 , 3 }))>);
左值情形7
相关:纯右值情形8 、亡值情形1
对象成员表达式a.m
,除了3种情况:
m
是成员枚举项。
m
是非静态成员函数。
a
是右值而m
是对象类型的非静态数据成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 struct foo { int m1 = 0 ; enum bar1 { m2 = 5 }; static int m3; static void f1 () {} void f2 () {} };int main () { foo a; static_assert (is_lvalue_v<decltype ((a.m1))>); static_assert (is_prvalue_v<decltype ((a.m2))>); a.f1; static_assert (is_lvalue_v<decltype ((a.f1))>); void (*p11)() = &foo::f1; void (*p12)() = &a.f1; void (foo::*p21)() = &foo::f2; void (foo::*p22)() = &a.f2; static_assert (is_xvalue_v<decltype ((foo ().m1))>); static_assert (is_lvalue_v<decltype ((foo ().m3))>); return 0 ; }
左值情形8
相关:纯右值情形9
内置的指针成员表达式p->m
,除了2种情况:
m
是成员枚举项。
m
是非静态成员函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 struct foo { int m1 = 0 ; enum bar1 { m2 = 5 }; static void f1 () {} void f2 () {} };int main () { foo *a = new foo (); static_assert (is_lvalue_v<decltype ((a->m1))>); static_assert (is_prvalue_v<decltype ((a->m2))>); a->f1; static_assert (is_lvalue_v<decltype ((a->f1))>); void (*p11)() = &foo::f1; void (*p12)() = &a->f1; void (foo::*p21)() = &foo::f2; void (foo::*p22)() = &a->f2; return 0 ; }
左值情形9
相关:纯右值情形10 、亡值情形2
对象的成员指针表达式a.*mp
,其中a
是左值且mp
是数据成员指针。
.*
运算符不可重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct foo { int m = 0 ; };int main () { foo a; int foo::*p = &foo::m; static_assert (is_lvalue_v<decltype ((a.*p))>); static_assert (is_xvalue_v<decltype ((foo ().*p))>); return 0 ; }
左值情形10
相关:纯右值情形11
内建的指针的成员指针表达式p->*mp
,其中mp
是数据成员指针。
注意,foo();
和new foo();
中构造的对象的生命周期不同,前者构造的对象在表达式结束时被销毁,后者构造的对象会一直存在并造成内存泄漏。
1 2 3 4 5 6 7 8 9 10 11 12 13 struct foo { int m = 0 ; };int main () { foo *a = new foo (); int foo::*p = &foo::m; static_assert (is_lvalue_v<decltype ((a->*p))>); static_assert (is_lvalue_v<decltype (((new foo ())->*p))>); return 0 ; }
左值情形11
相关:纯右值情形12 、亡值情形3
内建的逗号表达式a, b
,其中b
是左值。
1 2 int a = 0 ;static_assert (is_lvalue_v<decltype ((0 , a))>);
*左值情形12
相关:纯右值情形13 、亡值情形4
三元表达式a ? b : c
中b
和c
满足某些条件时,其为左值。
这条规则相当复杂,参见cppreference中的条件运算符条目 。
左值情形13
字符串字面量。
1 static_assert (is_lvalue_v<decltype (("go" ))>);
左值情形14
左值情形17 、纯右值情形14 、亡值情形7
转换到左值引用类型的转型表达式。
1 2 3 4 5 6 7 8 9 10 11 void foo (int a) { return ; }int main () { int x = 0 ; static_assert (is_lvalue_v<decltype ((static_cast <int &>(x)))>); static_assert (is_lvalue_v<decltype ((static_cast <void (&)(int )>(foo)))>); return 0 ; }
左值情形15
具有左值引用类型的非类型模板形参。
1 2 3 4 template <int & v>void set () { static_assert (is_lvalue_v<decltype ((v))>); }
左值情形16
相关:左值情形2 、纯右值情形2 、亡值情形5
返回类型是到函数的右值引用的函数调用表达式或重载的运算符表达式(C++11起)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <type_traits> 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 {};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 > constexpr bool is_lvalue_v = is_lvalue<T>::value;template <class T > constexpr bool is_xvalue_v = is_xvalue<T>::value;template <class T > constexpr bool is_prvalue_v = is_prvalue<T>::value;int test (int x) { return 0 ; }int (*get_test1 ())(int ) { return test; }int (&get_test2 ())(int ) { return test; }int (&&get_test3 ())(int ) { return test; }int main () { static_assert (is_prvalue_v<decltype ((get_test1 ()))>); static_assert (is_lvalue_v<decltype ((get_test2 ()))>); static_assert (is_lvalue_v<decltype ((get_test3 ()))>); return 0 ; }
左值情形17
左值情形14 、纯右值情形14 、亡值情形7
转换到函数的右值引用类型的转型表达式(C++11起)。
1 2 3 4 5 6 7 8 int test (int x) { return 0 ; }int main () { static_assert (is_lvalue_v<decltype ((static_cast <int (&&)(int )>(test)))>); return 0 ; }
纯右值情形
纯右值包含计算内建运算符的操作数的值 的表达式(无结果对象)和初始化对象 的表达式(有结果对象)。
纯右值情形1
除了字符串字面量之外的字面量。
1 2 3 static_assert (is_prvalue_v<decltype ((42 ))>);static_assert (is_prvalue_v<decltype ((true ))>);static_assert (is_prvalue_v<decltype ((nullptr ))>);
纯右值情形2
相关:左值情形16 、左值情形16 、亡值情形5
返回类型是非引用的函数调用或重载运算符表达式。
1 2 std::string str = "hello" ;static_assert (is_prvalue_v<decltype ((str.substr (1 , 2 )))>);
纯右值情形3
内建的后置自增与后置自减表达式。
1 2 3 int x = 0 ;static_assert (is_prvalue_v<decltype ((x--))>);static_assert (is_prvalue_v<decltype ((x++))>);
纯右值情形4
内建的算术表达式。
1 2 3 int x = 0 ;static_assert (is_prvalue_v<decltype ((x + x))>);static_assert (is_prvalue_v<decltype ((x % x))>);
纯右值情形5
内建的逻辑表达式。
1 2 bool a = true , b = false ;static_assert (is_prvalue_v<decltype ((a || b))>);
纯右值情形6
内建的比较表达式。
1 2 int a = 1 , b = 0 ;static_assert (is_prvalue_v<decltype ((a > b))>);
纯右值情形7
内建的取地址表达式。
1 2 int a = 1 ;static_assert (is_prvalue_v<decltype ((&a))>);
纯右值情形8
相关:左值情形7 、亡值情形1
对象成员表达式a.m
:
m
是成员枚举项。
m
是非静态成员函数。
纯右值情形9
相关:左值情形8
内置的指针成员表达式p->m
:
m
是成员枚举项。
m
是非静态成员函数。
纯右值情形10
相关:左值情形9 ,亡值情形2
对象的成员指针表达式a.*mp
,其中mp
是成员函数指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct foo { int m () { return 0 ; } };int main () { foo a; int (foo::*p)() = &foo::m; static_assert (is_prvalue_v<decltype ((a.*p))>); static_assert (is_prvalue_v<decltype ((foo ().*p))>); return 0 ; }
纯右值情形11
相关:左值情形10
内建的指针的成员指针表达式p->*mp
,其中mp
是成员函数指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct foo { int m () { return 0 ; } };int main () { foo *a = new foo (); int (foo::*p)() = &foo::m; static_assert (is_prvalue_v<decltype ((a->*p))>); static_assert (is_prvalue_v<decltype (((new foo ())->*p))>); return 0 ; }
纯右值情形12
相关:左值情形11 、亡值情形3
内建的逗号表达式a, b
,其中b
是纯右值。
1 2 int a = 0 ;static_assert (is_prvalue_v<decltype ((a, 0 ))>);
*纯右值情形13
相关:左值情形12 ,亡值情形4
三元表达式a ? b : c
中b
和c
满足某些条件时,其为纯右值。
纯右值情形14
左值情形14 、左值情形17 、亡值情形7
转换到非引用类型的转型表达式。
1 2 3 4 5 int a = 0 ;static_assert (is_prvalue_v<decltype ((static_cast <double >(a)))>);static_assert (is_prvalue_v<decltype ((std::string{}))>);static_assert (is_prvalue_v<decltype (((int )42 ))>);static_assert (is_prvalue_v<decltype ((int (42 )))>);
纯右值情形15
this
指针。
1 2 3 4 5 struct foo { foo () { static_assert (is_prvalue_v<decltype ((this ))>); } };
纯右值情形16
枚举项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 enum class Color { Red, Green, Blue };enum Fruit { Apple, Orange, Banana };int main () { static_assert (is_prvalue_v<decltype ((Color::Red))>); static_assert (is_prvalue_v<decltype ((Apple))>); return 0 ; }
纯右值情形17
具有标量类型(scalar type)的非类型模板形参。
1 2 3 4 5 6 7 8 9 template <int v>void foo () { const int * a = &v; v = 3 ; static_assert (is_prvalue_v<decltype ((v))>); }
纯右值情形18
lambda 表达式(C++11起)。
1 static_assert (is_prvalue_v<decltype (([](int x){ return x * x; }))>);
纯右值情形19
requires表达式,concept的特化(C++20起)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <type_traits> #include <concepts> template <typename T>concept Squarable = requires (T x) { { x * x } -> std::convertible_to<T>; };int main () { static_assert (Squarable<int >, "int should be squarable" ); static_assert (!Squarable<void >, "void should not be squarable" ); static_assert (is_prvalue_v<decltype ((Squarable<int >))>); return 0 ; }
亡值情形
亡值情形1
相关:左值情形7 、纯右值情形8
对象成员表达式a.m
:
a
是右值而m
是对象类型的非静态数据成员。
即左值的情形7的情况3。
亡值情形2
相关:左值情形9 、纯右值情形10
对象的成员指针表达式a.*mp
,其中a
是右值且mp
是数据成员指针。
亡值情形3
相关:左值情形11 、纯右值情形12
内建的逗号表达式a, b
,其中b
是亡值。
1 2 int a = 0 ;static_assert (is_xvalue_v<decltype ((0 , std::move (a)))>);
*亡值情形4
相关:左值情形12 、纯右值情形13
三元表达式a ? b : c
中b
和c
满足某些条件时,其为亡值。
亡值情形5
相关:左值情形2 、左值情形16 、纯右值情形2
返回类型是对象的右值引用的函数调用或重载运算符表达式(C++11起)。
1 2 int a = 0 ;static_assert (is_xvalue_v<decltype ((std::move (a)))>);
亡值情形6
相关:左值情形6
内建的下标表达式a[n]
,当它的操作数之一是数组右值时(C++11起)。
亡值情形7
相关:左值情形14 、左值情形17 、纯右值情形14
转换到对象的右值引用类型的类型转换表达式(C++11起)。
1 2 int a = 0 ;static_assert (is_xvalue_v<decltype ((static_cast <int &&>(a)))>);
亡值情形8
在临时量实质化 后,任何指代该临时对象的表达式。(C++17起)
临时量实质化,即任何完整类型T
的纯右值,可转换成同类型T
的亡值。此转换以该纯右值初始化一个T
类型的临时对象(以临时对象作为求值该纯右值的结果对象),并产生一个代表该临时对象的亡值。如果T
是类类型或类类型的数组,那么它必须有可访问且未被弃置的析构函数。
1 2 3 4 struct S { int m; };int k = S ().m; static_assert (is_prvalue_v<decltype ((S ()))>);static_assert (is_xvalue_v<decltype ((S ().m))>);
临时量实质化在下例情况下发生:
绑定引用到纯右值时。
访问类纯右值的数据成员时。
调用类纯右值的隐式对象成员函数时。
进行数组到指针转换或在数组纯右值上使用下标时。
以花括号初始化器列表初始化std::initializer_list<T>
类型的对象时。
纯右值作为弃值表达式时。
临时量实质化在从纯右值初始化同类型对象(由直接初始化或复制初始化)时不会发生:这种对象直接从初始化器初始化。这确保了“受保证的复制消除 ”。
亡值情形9
虽然如左值情形1 所说,由任何变量的名字构成的表达式是左值表达式,但若它作为:
return 语句
co_return语句 (C++20 起)
throw表达式 (C++17 起)
的操作数出现,则表达式具有移动资格 。
有移动资格的表达式在在重载决议时被视为亡值(C++23起)。C++23前,被视为左值或右值。
值类别的性质
泛左值
可以通过左值到右值、数组到指针或函数到指针隐式转换 转换成纯右值。
可以是多态 的:它标识的对象的动态类型不必是该表达式的静态类型。
可以具有不完整类型,只要表达式中容许。
右值
不能由内建的取址运算符取地址 :&int()
、&i++[3]
、&42
、&std::move(x)
是非法的。
不能用作内建赋值运算符及内建复合赋值运算符的左操作数。
可以用来初始化const
左值引用,这种情况下该右值所标识的对象的生存期 被延长到该引用的作用域结尾。
可以用来初始化右值引用,这种情况下该右值所标识的对象的生存期被延长到该引用的作用域结尾。(C++11起)
当被用作函数实参且该函数有两种重载可用,其中之一接受右值引用的形参而另一个接受const
的左值引用的形参时,右值将被绑定到右值引用的重载之上 (从而,当复制与移动构造函数均可用时,以右值实参将调用它的移动构造函数,复制和移动赋值运算符与此类似)。(C++11起)
左值
与泛左值相同。
可以通过内建的取址运算符取左值的地址 :&++i
(假设i
具有内建类型,或者它的前置自增运算符被重载为返回左值引用)及&std::endl
是合法表达式。
可修改的左值可用作内建赋值和内建复合赋值运算符的左操作数 。
可以用来初始化左值引用 ,这会将新名字关联给该表达式所标识的对象。
纯右值
与右值相同。
不具有多态 :它所标识的对象的动态类型始终是该表达式的类型。
非类非数组的纯右值不能有cv限定,【除非它被实质化以绑定到cv限定类型的引用(C++17起)】。(注意:函数调用或转型表达式可能生成非类的cv限定类型的纯右值,但它的cv限定符通常被立即剥除)
不能具有不完整类型(除了类型void
,或在decltype
说明符中使用之外)。
不能具有抽象类类型或它的数组类型。
亡值
与右值相同。
与泛左值相同。
与所有的右值类似,亡值可以绑定到右值引用上;并且与所有的泛左值类似,亡值可以是多态的,而且非类的亡值可以有cv限定。
特殊类别
位域
代表某个位域的表达式(例如a.m
,其中a
是类型struct A { int m: 3; }
的左值)是泛左值表达式:它可用作赋值运算符的左操作数,但它不能被取地址 ,并且非const
的左值引用不能绑定于它。const左值引用或右值引用可以从位域泛左值初始化,但这会制造位域的临时副本:它不会直接绑定到位域。
void
表达式
返回void
的函数调用表达式,转换到void
的转型表达式,以及throw
表达式,被归类为纯右值表达式,但它们不能用来初始化引用或者作为函数实参。它们可以用在舍弃值的语境(例如自成一行,作为逗号运算符的左操作数等)和返回void
的函数中的return
语句中。另外,throw
表达式可用作条件运算符?:
的第二个和第三个操作数。
未决成员函数调用
表达式a.mf
与p->mf
(其中mf
是非静态成员函数)和表达式a.*pmf
与p->*pmf
(其中pmf
是成员函数指针)被归类为纯右值表达式,但它们不能用来初始化引用,作为函数实参,或者用于除了作为函数调用运算符的左操作数(例如(p->*pmf)(args))
以外的任何目的。
参考资料