B 值类型

B.1 传统意义下的左值和右值

左值(lvalue)是在内存或者寄存器中的值。起初这种说法来源于C语言中在=左边的值,l表示left-hand side,相应的,右值(rvalue)来源于在=右边的值,r表示right-hand side。不过后来在C语言中某些被const修饰的变量也可以存在于内存或者寄存器中,但却是不可修改的。C++扩展了右值的含义,对于类的右值,其也是可以在=左边的,此时的=应该理解为重载的=运算符。

或许将左值中的l理解为局部化变量(localizable value)更合适,这样左值的含义就更加广泛了:

  • 表达式中的变量
  • 指针解引用后的对象
  • 数据成员
  • 返回左值引用的函数
  • 字符串字面值

右值就是纯粹数学意义上的、不会被存储的、只能使用一次的值。总之,所有的临时量都是右值,但是引用右值的引用不是右值。原文:

That is, all temporaries are rvalues. (That doesn’t apply to named references that refer to them, though.)

B.1.1 左值到右值的转换

对于两个左值xy,在表达式x = y中,y发生了的左值到右值的隐式转换(lvalue-to-rvalue conversion),这种转换保证了左值可以当作右值使用,以及机器会执行一条装载指令。

B.2 C++11后的值类型

下面是C++11扩展后的值类型:

  • glvalue,全称generalized lvalue,会被存储的值,包括lvalue和xvalue
  • prvalue,全称pure rvalue,求值结果被用来初始化一个对象,或者是运算符的求值结果
  • xvalue,全称eXpiring value,可以被重用的值,具有固定的生命周期,姑且称为到期值
  • lvalue,glvalue中非xvalue的部分
  • rvalue,包括xvalue和prvalue

左值的例子:

  • 变量和函数变量
  • 指针解引用的结果
  • 字符串字面值
  • 返回左值引用的函数返回值

右值的例子:

  • 大多数字面值
  • 取地址运算符的结果
  • 内置算术运算符的结果
  • 不返回引用的函数返回值
  • lambda表达式

到期值的例子:

  • 返回右值引用的函数,例如std::move
  • 对对象类型的右值引用的强制类型转换的结果

B.2.1 临时实体化(Temporary Materialization)

临时实体化是左值到右值转换的逆转换,可以将prvalue转换为xvalue,也就是用rvalue临时初始化一个xvalue,一般发生在下面的情况中:

  • 引用绑定到纯右值
  • 访问类的纯右值成员
  • 通过下标访问的纯右值数组(An array prvalue is subscripted)
  • 纯右值数组转换为指针
  • 放入std::initializer_list中的纯右值
  • 使用sizeoftypeid对纯右值进行求值
  • 表达式的最终求值结果,或者表达式被转换为void

这里有一个有趣的例子:

class N {
    public:
        N();
        N(N const&) = delete;   // this class is neither copyable ...
        N(N&&) = delete;        // ... nor movable
};

N make_N() {
    return N{};     // Always creates a conceptual temporary prior to C++17.
}                   // In C++17, no temporary is created at this point.

auto n = make_N();  // ERROR prior to C++17 because the prvalue needs a
                    // conceptual copy. OK since C++17, because n is
                    // initialized directly from the prvalue.

上面的代码是可以直接在n中构造对象的,但是在C++17之前,这是一个优化措施,语言不能提供保证,并且因为N{}是一个右值,在要调用类N的移动构造函数来初始化n时还会报错,这是因为移动构造函数被删除了。原文:

Prior to C++17, the prvalue Nfg produced a temporary of type N, but compilers were allowed to elide copies and moves of that temporary (which they always did, in practice). In this case, that means that the temporary result of calling make_N() can be constructed directly in the storage of n; no copy or move operation is needed. Unfortunately, pre-C++17 compilers still have to check that a copy or move operation could be made, and in this example that is not possible because the copy constructor of N is deleted (and no move constructor is generated). Hence, C++11 and C++14 compilers must issue an error for this example. With C++17 the prvalue N itself does not produce a temporary. Instead, it initializes an object determined by the context: In our example, that object is the one denoted by n. No copy or move operation is ever considered (this is not an optimization, but a language guarantee) and therefore the code is valid C++17.

最后是一个值类型的例子:

class X {
};

X v;
X const c;

void f(X const&);   // accepts an expression of any value category
void f(X&&);        // accepts prvalues and xvalues only but is a better match
                    // for those than the previous declaration

f(v);               // passes a modifiable lvalue to the first f()
f(c);               // passes a nonmodifiable lvalue to the first f()
f(X());             // passes a prvalue (since C++17 materialized as xvalue) to the 2nd f()
f(std::move(v));    // passes an xvalue to the second f()

B.3 通过decltype识别值类型

双括号版本的decltype((x))的结果为:

  • 如果x为纯右值,则为type
  • 如果x为左值,则为type&
  • 如果x为xvalue,则为type&&

B.4 引用类型

int& lvalue();
int&& xvalue();
int prvalue();

std::is_same_v<decltype(lvalue()), int&>    // yields true because result is lvalue
std::is_same_v<decltype(xvalue()), int&&>   // yields true because result is xvalue
std::is_same_v<decltype(prvalue()), int>    // yields true because result is prvalue

int& lref1 = lvalue();      // OK: lvalue reference can bind to an lvalue
int& lref3 = prvalue();     // ERROR: lvalue reference cannot bind to a prvalue
int& lref2 = xvalue();      // ERROR: lvalue reference cannot bind to an xvalue

int&& rref1 = lvalue();     // ERROR: rvalue reference cannot bind to an lvalue
int&& rref2 = prvalue();    // OK: rvalue reference can bind to a prvalue
int&& rref3 = xvalue();     // OK: rvalue reference can bind to an xrvalue

results matching ""

    No results matching ""