0
votes

This document appears to be the source of these changes from C++11: n3055.

II. Overview of Changes

Rvalues, as currently known in the core language clauses, are renamed to “prvalues” (“pure” rvalues).

lvalues meaning was not changed, but glvalues were introduced to cover xvalues.

Why not do the same for rvalues?!

rvalues meaning should have remain unchanged, but introduce grvalues to cover xvalues too. (prvalues could be replaced back to simple rvalues in symmetry with how lvalues are defined)

      expression
      /        \
   glvalue    grvalue
   /     \    /     \
lvalue   xvalue   rvalue

Value categories can be defined more simple:

  • an lvalue has identity but cannot be moved from; is a glvalue that is not an xvalue
  • an rvalue has no identity but can be moved from; is a grvalue that is not an xvalue
  • an xvalue has identity and can be moved from.

Every expression is either an lvalue, xvalue or rvalue.

1
I think your approach is circular. "Moving from" isn't a fundamental notion, but rather, a library convention built on the current type system and object model. Lvalues and rvalues are made precisely to allow lvalue and rvalue references.Kerrek SB

1 Answers

3
votes

The concept of rvalues was made more general, and the essence of rvalues was identified to be the absence of aliasing. A new value category, xvalues, was added that shares this property, and the subset of rvalues that was present prior to C++11 was renamed to prvalues ("pure rvalues").

The two primary divisions we want are now these:

  • glvalue vs prvalue: glvalues are objects (= storage locations); prvalues are the traditional "temporaries" (but see below).
  • lvalue vs rvalue: lvalues bind to lvalue references, rvalues bind to rvalue references. This distinction is the one needed to define how references and reference binding works.

An xvalue is both a glvalue and an rvalue. This reflects the fact that rvalue references are a way to perform lvalue-to-xvalue conversion (via cast, std::move), that is, to have user-defined rvalues. They also allow a conversion in the other direction, of prvalue to lvalue, by binding an rvalue to an rvalue reference (which is now an lvalue).

C++17 refines the idea a bit further by turning prvalues into "notional values" or "unmaterialized values" which are no longer objects. In this new picture, only glvalues are objects. Prvalues, however, can be materialized into objects, which is appropriately called "prvalue-to-glvalue conversion".

This new way of thinking about value categories means that if you have a function T f();, where T is not a reference, then the expression f() itself is not an object (hence does not necessitate copying of objects!), but when needed (e.g. if you say f().x), the an object is created and initialized with the prvalue (= materialized) so as to allow the member access.


Maybe it's valuable to summarize all the conversions that exist in C++17:

  • "glvalue-to-prvalue": This is what C calls the "lvalue conversion". Example: int a = 1; 1 + a; The second operand the addition expression is a prvalue, obtained by glvalue-to-prvalue conversion of a.

  • "rvalue-to-lvalue": Provided by rvalue references. Example: int && r = e. Here e is an rvalue expression, and r is an lvalue designating the same (or potentially materialized) object.

  • "prvalue-to-glvalue": This is materialization of a prvalue were a glvalue is expected.

  • "lvalue-to-rvalue": Only actually available as "lvalue-to-xvalue" via std::move, e.g. int a; static_cast<int&&>(a);.

Note that all the pairs that appear in the list have the same number of letters -- either both have one letter, or both have two. I think that's a consequence of the well-chosen taxonomy that your proposed alternative lacks.