Value Categories
Value categories - cppreference.com 有更详细的说明。
分类
lvalue / rvalue / xvalue 是互斥的,C++ 表达式只能是三者之一。glvalue 和 rvalue 都是复合类别:glvalue 包括 lvalue 和 xvalue,rvalue 包括 prvalue 和 xvalue。在翻译上,xvalue 叫(将)亡值,glvalue 叫泛左值。
一部分规则(一些理所当然的就在这里略去):
类型 | 规则 |
---|---|
lvalue | 具名的数据是 lvalue,比如变量名、函数名、template parameter object 名(C++ 20,见下方代码块)、左值引用类型的 NTTP 名。还有字符串字面量。 |
prvalue | 字面量、枚举名、lambda 表达式等。标量类型的 NTTP 名。C++ 20 后 requires 表达式和 concept 的特化也是 prvalue。 |
xvalue | rvalue 的成员访问(. / .* / -> / ->* )是 xvalue。 |
Template Parameter Object
在 C++ 20 中被改为 lvalue。
#include <iostream>
struct Foo {};
template <Foo v>
void set()
{
// 这里要求 v 是 lvalue,C++20 可以编译,C++17 不行
std::cout << &v << "\n";
}
void foo()
{
set<Foo{}>();
}
Concept 特化
template <typename T>
struct is_int { enum { value = false }; };
template <>
struct is_int<int> { enum { value = true }; };
static_assert(is_lvalue<decltype((std::is_scalar_v<int>))>::value);
static_assert(is_prvalue<decltype((std::equality_comparable<int>))>::value); // concept 特化
static_assert(is_prvalue<decltype((is_int<int>::value))>::value);
注意 concept 的特化是 prvalue,但是以往 trait 类中的静态成员(一般写成 static inline constexpr
)却是 lvalue!(除非 trait 类的 value 是用 enum 实现的,就像上面代码块的 is_int
一样)。
如何得到 xvalue
xvalue 是比较特殊的一种:给人的感觉是要么是转换过来的(人为标记),要么是直接访问右值成员(不是通过成员函数访问)得到的。用右值引用转换得到的是 xvalue!
template<typename T>
struct value_category {
// Or can be an integral or enum value
static constexpr auto value = "prvalue";
};
template<typename T>
struct value_category<T&> {
static constexpr auto value = "lvalue";
};
template<typename T>
struct value_category<T&&> {
static constexpr auto value = "xvalue";
};
// Double parens for ensuring we inspect an expression,
// not an entity
#define VALUE_CATEGORY(expr) value_category<decltype((expr))>::value
这里照着写了一下:
struct str {
str() = default;
explicit str(const char *s) : start(strdup(s)) {}
// overloaded operators
char &operator[size_t index](size_t index) { return start.get()[index]; }
str &operator=(const char *s) { start.reset(strdup(s)); return *this; }
// data
struct freer { void operator()(void *p) { free(p); } };
std::unique_ptr<char, freer> start;
};
结果:
str{} => prvalue
str{} = "" => lvalue
str{}[0] => lvalue
str{}.start => xvalue
也可见经过运算符重载的访问不遵循 xvalue 的生成规则。
其他
如果是函数调用或者强制类型转换,则尊重其返回类型。如果用的是未重载的逗号操作符,则返回最右边的类型。如果是三元条件表达式,则返回两个分支的公共类型。
Pending member function call 是纯右值,但是除非用来调用,否则不能出现在表达式中,比如 a.f
不合法,而 a.f()
合法,其中 f
是类型 decltype(a)
的一个成员函数。
Void expressions 是 prvalue。
左值对象的 Bit-fields 是泛左值,能被赋值,但不能取地址,也不能绑定在非常量的引用上。对位域的常量引用或者右值引用会创建一份临时拷贝,不会真正绑定在位域上。
Move-eligible expressions 指的是在 return
/ co_return
/ throw
等位置出现的左值,它们视情况被当作 lvalue 或者 rvalue 看待(C++ 23 之前),或者被当作 rvalue 看待(C++ 23 及之后)。见
P2266R3 Simpler implicit move 对 C++23 函数返回表达式值类别的改变。
C++ 20 之后,template parameter object 也是 lvalue,注意得是 object,标准类型不行。
左右值的转换要用 static_cast
来实现,reinterpret_cast
做不到。