Value Categories

https://oneraynyday.github.io/dev/2020/07/03/Value-Categories/
https://en.cppreference.com/w/cpp/language/value_category

History

C++98

C++98 中有 lvalue 和 rvalue 的概念,这个时候的概念很单纯

int a; // a is an lvalue
int* b; // *b is an lvalue
int c[10]; // c[0] is an lvalue
struct e { int x; }; // e.x is an lvalue
void f() {} // f is an lvalue

lvalue-to-rvalue conversion:在 a = b 执行过程中左值表达式 b 被”转化“为右值表达式,加载 b 的值到寄存器然后赋予 a 所在的地址。

Reference 被引入

Reference 被引入的原因一方面是为了支持更优越的操作符重载(因为传参类型不能是同类型的指针,如果不使用引用只能使用复制传参),另一方面是因为在不引入 Reference 的情况下有些操作符重载不可实现(需要对传入对象进行修改的操作)

struct integer { 
    integer(int v) : value(v) {}
    int value; 
};

// This doesn't actually modify i
void operator++(integer i) {
    i.value++;
}

// This is ill-formed and won't compile -
// There is already an operator++ for integer*
// which moves the pointer itself.
void operator++(integer* i) { 
    *i.value++;
}

// This is our only option.
void operator++(integer& i) { 
    i.value++;
}

Reference 的局限

// bad! doesn't work for all situations!
// It works for:
//     integer i1(2);
//     integer i2(3);
//     i1 + i2;
// integer(2) + integer(3) should yield integer(5)
// but these are rvalues, so it won't compile!
integer operator+(integer& i1, integer& i2) {
    return integer(i1.value + i2.value);
}

// This is our only option.
integer operator+(const integer& i1, const integer& i2) {
    return integer(i1.value + i2.value);
}

引入 const reference 的原因

const reference 对右值传入参数的行为:The reason that only const integer& works like this is because of temporary materialization which binds the temporary integer(2) and integer(3) to i1 and i2 respectively, and they will exist in memory somewhere.

const reference 对右值传参的行为与右值的定义相悖:The fact that sometimes rvalues could “materialize” and exist in storage in the limited scope of the function, and sometimes not have storage at all must be confusing. 但是 const reference 和 non-const reference 的行为差异非常地反直觉,解决方案是将 rvalue 拆分为 xvalue 和 prvalue:This is why C++ further split up the concept of rvalues and defined the materialized values as xvalues, for “expiring values” and no-storage values as prvalues, for “pure rvalues”.

C++11 引入 move semantics 进一步复杂化 value categories

移动语义引入的动机是简化大型数据的构造,原来只能分配新内存、复制一份新的然后删除旧的,现在可以直接把旧内存拿来使用。注意构造的次数是不变的,变的只是对传入右值引用的构造函数调用的复杂度。移动语义的窗口包括移动构造函数和移动赋值函数。

引入移动语义前:

现在,移动后的左值属于什么 Value Categories,与 rvalue 拆分的命名规则不同,原来的 lvalue 变成了 glvalue,我们预想的 plvalue 变成了 lvalue。

注意各种值类型的定义的细节在不断改变

C++17

In C++17, copy ellision is now guaranteed for function calls returning prvalues, as in they never undergo temporary materialization.

RVO

General

https://oneraynyday.github.io/dev/2020/07/03/Value-Categories/
https://en.cppreference.com/w/cpp/language/value_category
Value Categories 谈论的对象是表达式。分类如下
Value Categories Classification.png

Left Operand of the Assignment Operator

prvalue left operand no error stackoverflow

标准只限制了 builtin assignment operator 的 left operand 必须是 modifiable lvalue,在 Value Categories cppreference 给出了一个 prvalue 的反例,在 std::tie cppreference 中给出了有实际应用空间的例子。prvalue left operand no error stackoverflow 中清晰解释了普遍的 prvalue as left operand of assignment 并不一定是错误。目前了解到会报错的情况有