The meaning of value categories has changed over time and can be quite a confusing topic, prone to misconception. This post will try to illuminate what, in C++17, these oddly named things actually are.

The most important thing to remember is that value categories are a taxonomy of expressions. They are not categories of objects or variables or types. Getting this wrong is an immediate source of problems. Consider:

The variable r is an rvalue reference. But the expression r on line 5 is an lvalue. As such, #1 is invoked. It doesn’t matter that the type of r matches (exactly, even) #2. The value category mismatch prevents that candidate from being viable.

Each expression, from the arbitrary complex down to the simple identifiers or literals, has a category that it corresponds to. This taxonomy can, to me, best be viewed as a Venn diagram:

That is, every expression is exactly one of an lvalue, an xvalue, or a prvalue. Those three are, in turn, subsets of two broader value categories: glvalues (the union of lvalues and xvalues) and rvalues (the union of xvalues and prvalues). Besides alphabet soup, what do these terms actually mean? In the new standard, the definitions are quite helpful. The three key ones are, from [basic.lval] (the other two are defined by their position in the Venn diagram):

A glvalue is an expression whose evaluation determines the identity of an object, bit-field, or function.

A prvalue is an expression whose evaluation initializes an object or a bit-field, or computes the value of the operand of an operator, as specified by the context in which it appears.

An xvalue is a glvalue that denotes an object or bit-field whose resources can be reused (usually because it is near the end of its lifetime).

A common way of describing these categories used to involve describing the higher-level groupings by what properties those expressions have. That is:

I think this is still a helpful way of thinking about the categories, even if it’s not strictly accurate anymore. The “has identity” part remains valid — indeed the standard definition of glvalue itself is basically this criteria.

But a big part of the change in P0135 (“Guaranteed copy elision through simplified value categories”) is that prvalues themselves do not necessarily have to lead to the existence of objects (only if a “temporary materialization” is necessary) and as such, it doesn’t quite make sense to talk about whether those resources can be safely reused. Indeed, C++17, prvalues are not moved from! Let’s take a seemingly simple example:

T var = T();

For some type T . In C++03, the expression T() is an rvalue, but this is copy-construction of a new variable named var . In C++11, the expression T() is a prvalue, and this is move-construction. In both cases, the copy/move will likely get elided, even if there are side effects. However, in C++17, there is no move. It’s important to repeat this for emphasis. The prvalue is not moved from. This is value-initializing var and is exactly equivalent to:

T var();

(Or, at least, would be if the above weren’t a function declaration. What T var = T() means today is declaring a variable var of type T constructed using the initializer () . There is not an easy way to express that in other terms).

The difference may seem academic. What’s the difference between move construction that the compiler elided, and just no move construction at all? Consider one of the motivating examples of P0135:

In C++14, this code is ill-formed, and the comments say it all. The move constructor must be valid, even if you don’t need. In C++17, this is no longer true, and the code is fine.