What is an expression?

It’s a bit of code that returns a value, something like

int i = 0;

Here an expression returns a variable, i, with a value of 0. Expressions can be literals, like 0, or variables, like i, but they can also be references, pointers, etc. Values are defined by two independent properties:

  • has identity
  • can be moved

What are the value categories?

Before C++11, there were L values and R values. These came from C and you probably don’t even think too much about them. L = R. Left Side Value = Right Side Value. However, these don’t quite cover all possible values. Using the properties above, an L value has an identity but cannot be moved and an R value can be moved but doesn’t have an identity. So, what about a value that has an identity and can be moved or a value that has no identity and cannot be moved?

This last one is pretty useless, but the former can be useful. It doesn’t quite fit in the small world of L and R values. So, in C++11 more thought was put into these values and what they could be used for. A value that has an identity and can be moved belongs to the X values.

int lvalue = 0;
int&& xvalue = std::move(lvalue);
//no longer safe to use lvalue.

Here, std::move is used to cast lvalue into an xvalue and avoids any copying. It’s important to note that std::move() doesn’t perform a move operation, it casts the value to an x value which can be moved.

It’s not too uncommon to see this mistake being made:

Type function() {
    Type result {};
    return std::move(result);

Usually, this is done as the author wants to take advantage of move semantics and avoid an unnecessary copy. Incidentally, as the return type for this function is an l value and the returned object has just been cast to an x value, it will now create a copy of it to turn it into an l value. So, by trying to avoid making a copy, a copy has been made. I made this mistake a few years ago when learning about move semantics and wanting to take advantage of them - I want to move it, so I std::move() it.

What I wanted to do was make use of return value optimization, a case of copy elision, which would look like this:

Type function() {
    Type result {};
    return result;

Here, the compiler is likely to apply the return value optimization and construct the returned object at the correct memory place where the function was called. This avoids making any copies, moves or temporary copies improving performance and memory.

A Breakdown of their meanings

Let’s dig more into what the value categories are:

     lvalue                  xvalue                  prvalue
        |                      |                        |
        |                      |                        |
        +------- glvalue ------+--------- rvalue -------+

On the left side, there are lvalues - left hand values. These can be glvalues - generalized left hand values. These are either L values we know and love - have identity and cannot be moved

On the right side are the right hand values, rvalues. These can be prvalues - pure right hand values - no identity and can be moved.

In the middle is the xvalue, which means eXpiring value. These are special types of glvalues and rvalues but are not lvalues or prvalues. They have identity like lvalues, unlike prvalues, and can be moved like prvalues unlike lvalues.

If you’re like me, you probably wander why isn’t it e-value for expiring? And you probably run into a lot of dead ends, ask a chat bot which gives you a load of rubbish then find an answer in a pdf by bjarne strousoup - “We didn’t find any real constraints on the naming to guide us, so we picked ‘x’ for the center, the unknown, the strange, the xpert only, or even x-rated.” (page 4).

References and Resources