0%

Value Categories in C++ - The Full Picture

This is my learning notes from the course - Mastering C++ Standard Library Features.

Xvalues? Glvalues? Prvalue?

Last time, we talked about Value Categories and Move Semantics in C++, and got some good ideas on lvalues and rvalues with examples. Today, let’s dig into more details on value categories in C++.

While knowledge of lvalues and rvalues is almost always enough for day-to-day coding, it provides a simplified model of C++ value categories.

Knowledge of the full picture is helpful to get a deeper understanding of move semantics and to write advanced code.

Value Categories (Simplified)

1
2
3
4
5
6
7
8
     Expression
a + 5 * b

/ \
/ \
/ \

Lvalue Rvalue

Value Categories (Detailed)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
          Expression
a + 5 * b

/ \
/ \
/ \

Glvalue Rvalue

/ \ / \
/ \ / \
/ \ / \

Lvalue Xvalue Prvalue

The terms lvalue and rvalue were not enough when designing C++11’s move semantics. Unconditionally moving rvalues would have caused potentially dangerous behavior, so a more fine-grained model was required.

There now are three leaf value categories:

  • lvalues
  • xvalues (“expiring rvalues”)
  • prvalues (“pure rvalues”)

Both xvalues and lvalues are “glvalues“.
Both xvalues and prvalues are “rvalues

Glvalue => *”has identity”*
Rvalue => *”cab be moved from”*

Let’s see an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <utility>

struct foo {};

foo prvalue(); // return generic foo type by value
foo& lvalue(); // return foo type by lvalue reference
foo&& xvalue(); // return foo type by rvalue reference

int main()
{
// The following expression is a `prvalue`, because `foo` has no identity
// and can be moved from
prvalue();

// The following expression is an `lvalue`, because `foo&` has identity
// (refers to something) and cannot be moved (is an lvalue reference)
lvalue();

// The following expression is a xvalue, because `foo&&` has identity
// (refers to something) and can be moved (is an rvalue reference)
xvalue();

// http://en.cppreference.com/w/cpp/language/value_category
}

There is another example:

1
2
3
4
5
6
int main()
{
foo f;
f; // `f` is an lvalue
std::move(f); // `std::move(f)` is an xvalue
}

An xvalue can be obtained from an lvalue, but not a prvalue. That’s why it stands for *”pure rvalue”*.

You can think of prvalues as rvalues that are not references (no identity).