What are rvalues, lvalues, xvalues, glvalues, and prvalues?

Viewed 225953

In C++03, an expression is either an rvalue or an lvalue.

In C++11, an expression can be an:

  1. rvalue
  2. lvalue
  3. xvalue
  4. glvalue
  5. prvalue

Two categories have become five categories.

  • What are these new categories of expressions?
  • How do these new categories relate to the existing rvalue and lvalue categories?
  • Are the rvalue and lvalue categories in C++0x the same as they are in C++03?
  • Why are these new categories needed? Are the WG21 gods just trying to confuse us mere mortals?
13 Answers

Why are these new categories needed? Are the WG21 gods just trying to confuse us mere mortals?

I don't feel that the other answers (good though many of them are) really capture the answer to this particular question. Yes, these categories and such exist to allow move semantics, but the complexity exists for one reason. This is the one inviolate rule of moving stuff in C++11:

Thou shalt move only when it is unquestionably safe to do so.

That is why these categories exist: to be able to talk about values where it is safe to move from them, and to talk about values where it is not.

In the earliest version of r-value references, movement happened easily. Too easily. Easily enough that there was a lot of potential for implicitly moving things when the user didn't really mean to.

Here are the circumstances under which it is safe to move something:

  1. When it's a temporary or subobject thereof. (prvalue)
  2. When the user has explicitly said to move it.

If you do this:

SomeType &&Func() { ... }

SomeType &&val = Func();
SomeType otherVal{val};

What does this do? In older versions of the spec, before the 5 values came in, this would provoke a move. Of course it does. You passed an rvalue reference to the constructor, and thus it binds to the constructor that takes an rvalue reference. That's obvious.

There's just one problem with this; you didn't ask to move it. Oh, you might say that the && should have been a clue, but that doesn't change the fact that it broke the rule. val isn't a temporary because temporaries don't have names. You may have extended the lifetime of the temporary, but that means it isn't temporary; it's just like any other stack variable.

If it's not a temporary, and you didn't ask to move it, then moving is wrong.

The obvious solution is to make val an lvalue. This means that you can't move from it. OK, fine; it's named, so its an lvalue.

Once you do that, you can no longer say that SomeType&& means the same thing everwhere. You've now made a distinction between named rvalue references and unnamed rvalue references. Well, named rvalue references are lvalues; that was our solution above. So what do we call unnamed rvalue references (the return value from Func above)?

It's not an lvalue, because you can't move from an lvalue. And we need to be able to move by returning a &&; how else could you explicitly say to move something? That is what std::move returns, after all. It's not an rvalue (old-style), because it can be on the left side of an equation (things are actually a bit more complicated, see this question and the comments below). It is neither an lvalue nor an rvalue; it's a new kind of thing.

What we have is a value that you can treat as an lvalue, except that it is implicitly moveable from. We call it an xvalue.

Note that xvalues are what makes us gain the other two categories of values:

  • A prvalue is really just the new name for the previous type of rvalue, i.e. they're the rvalues that aren't xvalues.

  • Glvalues are the union of xvalues and lvalues in one group, because they do share a lot of properties in common.

So really, it all comes down to xvalues and the need to restrict movement to exactly and only certain places. Those places are defined by the rvalue category; prvalues are the implicit moves, and xvalues are the explicit moves (std::move returns an xvalue).

As the previous answers exhaustively covered the theory behind the value categories, there is just another thing I'd like to add: you can actually play with it and test it.

For some hands-on experimentation with the value categories, you can make use of the decltype specifier. Its behavior explicitly distinguishes between the three primary value categories (xvalue, lvalue, and prvalue).

Using the preprocessor saves us some typing ...

Primary categories:

#define IS_XVALUE(X) std::is_rvalue_reference<decltype((X))>::value
#define IS_LVALUE(X) std::is_lvalue_reference<decltype((X))>::value
#define IS_PRVALUE(X) !std::is_reference<decltype((X))>::value

Mixed categories:

#define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))
#define IS_RVALUE(X) (IS_PRVALUE(X) || IS_XVALUE(X))

Now we can reproduce (almost) all the examples from cppreference on value category.

Here are some examples with C++17 (for terse static_assert):

void doesNothing(){}
struct S
{
    int x{0};
};
int x = 1;
int y = 2;
S s;

static_assert(IS_LVALUE(x));
static_assert(IS_LVALUE(x+=y));
static_assert(IS_LVALUE("Hello world!"));
static_assert(IS_LVALUE(++x));

static_assert(IS_PRVALUE(1));
static_assert(IS_PRVALUE(x++));
static_assert(IS_PRVALUE(static_cast<double>(x)));
static_assert(IS_PRVALUE(std::string{}));
static_assert(IS_PRVALUE(throw std::exception()));
static_assert(IS_PRVALUE(doesNothing()));

static_assert(IS_XVALUE(std::move(s)));
// The next one doesn't work in gcc 8.2 but in gcc 9.1. Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x)); 

The mixed categories are kind of boring once you figured out the primary category.

For some more examples (and experimentation), check out the following link on compiler explorer. Don't bother reading the assembly, though. I added a lot of compilers just to make sure it works across all the common compilers.

These are terms that the C++ committee used to define move semantics in C++11. Here's the story.

I find it difficult to understand the terms given their precise definitions, the long lists of rules or this popular diagram:

value categories traditional diagram

It's easier on a Venn diagram with typical examples:

value categories Venn diagram

Basically:

  • every expression is either lvalue or rvalue
  • lvalue must be copied, because it has identity, so can be used later
  • rvalue can be moved, either because it's a temporary (prvalue) or explicitly moved (xvalue)

Now, the good question is that if we have two orthogonal properties ("has identity" and "can be moved"), what's the fourth category to complete lvalue, xvalue and prvalue? That would be an expression that has no identity (hence cannot be accessed later) and cannot be moved (one need to copy its value). This is simply not useful, so hasn't been named.

This is Venn diagram I made for a highly visual C++ book I'm writing which I will be publishing on leanpub during development soon.

Venn Diagram showing the relationship between the value categories in C++

The other answers go into more detail with words, and show similar diagrams. But hopefully this presentation of the information is fairly complete and useful for referencing, in addition.

The main takeaway for me on this topic is that expressions have these two properties: identity and movability. The first deals with the the "solidness" with which something exists. That's important because the C++ abstract machine is allowed to and encouraged to aggressively change and shrink your code through optimizations, and that means that things without identity might only ever exist in the mind of the compiler or in a register for a brief moment before getting trampled on. But a piece of data like that is also guaranteed not to cause issues if you recycle it's innards since there's no way to try to use it. And thus, move semantics were invented to allow us to capture references to temporaries, upgrading them to lvalues and extending their lifetime.

Move semantics originally were about not just throwing away temporaries wastefully, but instead giving them away so they can consumed by another.

When you want to eat the rest of your friend's cornbread so it don't go to waste.

When you give your cornbread away, the person you give it to now owns it. They consume it. You should not attempt to eat or digest said cornbread once you've given it away. Maybe that cornbread was headed for the trash anyway, but now it's headed for their bellies. It's not yours anymore.

In C++ land, the idea of "consuming" a resource means that resource is now owned by us and so we should do any cleanup necessary, and ensure the object isn't accessed elsewhere. Often times, that means borrowing the guts to create new objects. I call that "donating organs". Usually, we are talking about pointers or references contained by the object, or something like that, and we want to keep those pointers or references around because they refer to data elsewhere in our program that is not dying.

Thus you could write a function overload that takes an rvalue reference, and if a temporary (prvalue) were passed in, that's the overload that would be called. A new lvalue would be created upon binding to the rvalue reference taken by the function, extending the life of the temporary so you could consume it within your function.

At some point, we realized that we often had lvalue non-temporary data that we were finished with in one scope, but wanted to cannibalize in another scope. But they aren't rvalues and so wouldn't bind to an rvalue reference. So we made std::move, which is just a fancy cast from lvalue to rvalue reference. Such a datum is an xvalue: a former lvalue now acting as if it were a temporary so it can also be moved from.

Related