Question

C++ Value Categories Explained: lvalues, rvalues, xvalues, glvalues, and prvalues

cppc++11expressionc++-faqvalue-categories

Question

In C++03, an expression is classified as either an lvalue or an rvalue. In C++11 and later, the model expands to include these categories:

  • lvalue
  • rvalue
  • xvalue
  • glvalue
  • prvalue

This raises a few important questions:

  • What does each of these expression categories mean?
  • How do the newer categories relate to the older lvalue and rvalue model from C++03?
  • Do lvalue and rvalue mean exactly the same thing in modern C++ as they did in C++03?
  • Why was this expanded classification introduced at all?

For example, modern C++ code often involves move semantics and references:

#include <utility>
#include <string>

std::string make_name() {
    return "Ada";
}

int main() {
    std::string a = "Hello";          // named object
    std::string b = make_name();       // temporary result
    std::string c = std::move(a);      // cast to movable value
}

Understanding the value category of expressions like a, make_name(), and std::move(a) is essential for understanding overload resolution, move semantics, and reference binding in modern C++.

Short Answer

By the end of this page, you will understand what C++ value categories are, how lvalue, rvalue, xvalue, glvalue, and prvalue fit together, and why C++11 introduced a more precise model. You will also see how these categories affect references, temporaries, moves, and real-world code.

Concept

C++ uses value categories to describe what kind of expression you are dealing with.

At a beginner level, the most important question is:

  • Does this expression refer to an object with identity that I can keep referring to?
  • Or is it more like a temporary value or computed result?

Before C++11, people mostly used two terms:

  • lvalue
  • rvalue

That worked for many situations, but modern C++ introduced move semantics, which required a more precise distinction. In particular, C++ needed a way to describe values that:

  • still refer to an object,
  • but are also safe to treat as something that can be moved from.

That is where the newer categories come from.

The five categories

lvalue

An expression that refers to an object or function with identity.

Examples:

int x = 10;
x;          // lvalue

You can usually take its address:

&x;

prvalue

A pure rvalue. This is typically a temporary value or a computed result that does not name a persistent object in the same way an lvalue does.

Examples:

42;               // prvalue
x + 1;            // prvalue
std::string("A"); // prvalue

xvalue

An expiring value. This is a kind of expression that refers to an object with identity, but one whose resources may be reused because it is near the end of its lifetime.

Examples:

std::move(x);     // xvalue

This is the category that made move semantics practical.

glvalue

A generalized lvalue. This is a broader category that includes:

  • lvalue
  • xvalue

A glvalue is basically an expression that identifies an object or function.

rvalue

In modern C++, rvalue is a broader category that includes:

  • prvalue
  • xvalue

So in C++11 and later, rvalue no longer means exactly what many people informally meant in older C++ discussions.

The hierarchy

The easiest way to remember the relationships is:

  • glvalue = lvalue or xvalue
  • rvalue = prvalue or xvalue

That means xvalue sits in the overlap between “has identity” and “can be moved from”.

Why this matters

These categories affect:

  • which function overload is called,
  • whether a value binds to T&, const T&, or T&&,
  • whether the compiler copies or moves an object,
  • how temporaries behave.

Without these distinctions, it would be hard to describe modern C++ rules precisely.

Mental Model

Think of C++ expressions as items in a workspace.

  • An lvalue is like a labeled storage box on your desk. It has a clear identity and location.
  • A prvalue is like a freshly calculated number written on a sticky note. It is just a value.
  • An xvalue is like a labeled box that you have marked “about to be emptied—safe to take contents”.
  • A glvalue means “something with identity”.
  • An rvalue means “something usable as a temporary or movable value”.

So:

  • lvalue = named box
  • prvalue = temporary note
  • xvalue = named box marked for transfer

This explains why std::move(x) is special: it does not move by itself. It simply marks x as an expiring value, telling C++ that its contents may be moved from.

Take Quiz

Syntax and Examples

Core examples

#include <iostream>
#include <string>
#include <utility>

std::string make_text() {
    return "hello";   // prvalue
}

int main() {
    std::string s = "world";

    s;                 // lvalue
    make_text();       // prvalue
    std::move(s);      // xvalue
}

Reference binding helps reveal categories

int x = 5;

int& a = x;        // OK: lvalue reference binds to lvalue
// int& b = 10;    // Error: non-const lvalue reference cannot bind to prvalue

const int& c = 10; // OK: const lvalue reference can bind to prvalue
int&& d = 10;      // OK: rvalue reference binds to prvalue
int&& e = std::move(x); // OK: std::move(x) is an xvalue

Overload example

#include <iostream>
#include <utility>

void f(int& ) {
    std::cout << "lvalue\n";
}

void f(int&&) {
    std::cout << "rvalue\n";
}

int main() {
    int x = 1;
    f(x);            // lvalue
    f(2);            // rvalue (prvalue)
    f(std::move(x)); // rvalue (xvalue)
}

What this shows

  • x is an lvalue because it names an object.
  • 2 is a prvalue.
  • std::move(x) is an xvalue.
  • Both 2 and std::move(x) can bind to int&&, because both are rvalues in the modern sense.

Step by Step Execution

Consider this example:

#include <iostream>
#include <utility>

void inspect(int& )  { std::cout << "lvalue\n"; }
void inspect(int&&)  { std::cout << "rvalue\n"; }

int main() {
    int x = 10;
    inspect(x);
    inspect(10);
    inspect(std::move(x));
}

Step by step:

  1. int x = 10;

    • x is a named object.
    • The expression x is an lvalue.
  2. inspect(x);

    • Overload resolution checks both functions.
    • inspect(int&) matches lvalues.
    • inspect(int&&) does not bind to an lvalue.
    • Output: lvalue
  3. inspect(10);

    • 10 is a literal.
    • It is a prvalue.
    • A prvalue is an rvalue.
    • It binds to int&&.
    • Output: rvalue
  4. inspect(std::move(x));

    • std::move(x) converts x into an expression treated as an xvalue.
    • An xvalue is also an rvalue.
    • It binds to int&&.
    • Output: rvalue

Final output:

lvalue
rvalue
rvalue

Important detail:

int&& ref = 10;
ref; // this expression is an lvalue

Even though ref has type int&&, the expression ref is still an lvalue because it is a named variable. This is a very common source of confusion.

Real World Use Cases

1. Move semantics in containers

Standard library containers use value categories to decide whether to copy or move objects.

std::string s = "hello";
std::vector<std::string> v;
v.push_back(s);            // copies from lvalue
v.push_back(std::move(s)); // moves from xvalue

2. Returning objects from functions

Functions often return temporary objects.

std::string build_message() {
    return "done";
}

The returned expression is treated as a temporary value, which allows efficient construction and movement.

3. Overload resolution

APIs sometimes offer separate overloads for reusable objects and temporary objects.

void set_name(const std::string& s); // read from existing object
void set_name(std::string&& s);      // take ownership efficiently

4. Perfect forwarding and generic code

Templates preserve value categories so wrappers do not accidentally turn rvalues into lvalues.

template <typename T>
void wrapper(T&& value) {
    consume(std::forward<T>(value));
}

5. Resource-managing classes

Classes that manage memory, file handles, sockets, or buffers rely on xvalues and rvalue references to transfer ownership cheaply.

Take Quiz

Real Codebase Usage

In real C++ codebases, developers rarely talk about glvalue and prvalue every day, but they constantly rely on the rules behind them.

Common patterns

Overloads for copy vs move

class Buffer {
public:
    void setData(const std::string& s) {
        data = s; // copy
    }

    void setData(std::string&& s) {
        data = std::move(s); // move
    }

private:
    std::string data;
};

Move constructors and move assignment

class FileHandle {
public:
    FileHandle(FileHandle&& other) noexcept {
        fd = other.fd;
        other.fd = -1;
    }

private:
    int fd = -1;
};

These are used when an object is an rvalue, especially an xvalue.

Perfect forwarding in helpers

template <typename T, typename... Args>
std::unique_ptr<T> make_object(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

Guarding against accidental copies

Developers use std::move when they want to explicitly allow a named object to be treated as movable.

result.push_back(std::move(item));

Without std::move, item is an lvalue, so many APIs would copy instead of move.

Important practical idea

The expanded value-category model exists mainly so the language can describe these patterns precisely and consistently.

Common Mistakes

1. Thinking std::move actually moves the object

std::move does not move anything by itself. It only casts to an xvalue.

std::string a = "hello";
std::string b = std::move(a); // move may happen here, in the constructor

The actual move happens only if a move constructor or move assignment operator is used.

2. Believing that every T&& expression is an rvalue

Broken reasoning:

int&& r = 5;
// r is declared as int&&, so it must be an rvalue... right?

Wrong. The expression r is an lvalue because it is named.

int&& r = 5;
// int&& x = r; // Error: r is an lvalue expression
int&& x = std::move(r); // OK

3. Confusing xvalue with all rvalues

Not every rvalue is an xvalue.

42;            // prvalue
std::move(x);  // xvalue

Both are rvalues, but they are not the same subcategory.

4. Assuming C++03 and modern rvalue mean exactly the same thing

They are related, but the newer model is more precise. In modern C++, rvalue is split into prvalue and xvalue.

5. Using moved-from objects without care

std::string s = "abc";
std::string t = std::move(s);

After this, s is valid but its contents are unspecified. Do not assume it still contains "abc".

6. Forgetting that const can block moving

const std::string s = "hello";
std::string t = std::move(s); // usually copies, not moves

Many move operations need a non-const object because moving often modifies the source.

Comparisons

Category relationships

CategoryHas identity?Can appear as movable temporary?Includes
lvalueYesNonamed objects, functions
prvalueUsually no direct reusable identity in the old senseYesliterals, computed results, temporaries
xvalueYesYesstd::move(x), certain returned objects
glvalueYesSometimeslvalue + xvalue
rvalueNot necessarily identity-basedYesprvalue + xvalue

Old vs modern model

C++03 ideaModern C++ equivalent
lvaluestill exists, but now part of glvalue
rvaluesplit more precisely into prvalue and xvalue

Reference binding comparison

Reference typeBinds to lvalue?Binds to prvalue?Binds to xvalue?
T&YesNoNo
const T&YesYesYes
T&&NoYesYes

Why not just keep lvalue/rvalue only?

Because move semantics needed the language to distinguish:

  • ordinary named objects (lvalue)
  • temporary pure values (prvalue)
  • objects that still have identity but are safe to treat as expiring (xvalue)
Take Quiz

Cheat Sheet

Quick rules

  • lvalue = named object or function, has identity
  • prvalue = pure temporary value or computed result
  • xvalue = expiring value, usually produced by std::move
  • glvalue = lvalue or xvalue
  • rvalue = prvalue or xvalue

Remember this hierarchy

glvalue
├── lvalue
└── xvalue

rvalue
├── prvalue
└── xvalue

Common examples

int x = 1;

x;              // lvalue
1;              // prvalue
x + 1;          // prvalue
std::move(x);   // xvalue

Reference binding

int x = 1;
int& a = x;              // OK
const int& b = 1;        // OK
int&& c = 1;             // OK
int&& d = std::move(x);  // OK

Important gotcha

int&& r = 1;
r; // lvalue expression because it has a name

Why the extra categories exist

They allow C++ to describe:

  • move semantics
  • overload resolution
  • temporary objects
  • perfect forwarding
  • reference binding rules

more precisely than the old two-category model.

FAQ

What is the difference between prvalue and xvalue in C++?

A prvalue is a pure temporary value like 42 or x + 1. An xvalue refers to an existing object that is treated as expiring, such as std::move(x).

Is std::move(x) an rvalue or an xvalue?

It is an xvalue, and since xvalue is a kind of rvalue, it is both.

Is a named rvalue reference an rvalue?

No. A named variable is an lvalue expression, even if its type is T&&.

Why did C++11 add glvalue and prvalue?

To describe modern language behavior precisely, especially move semantics, reference binding, and overload resolution.

Are lvalue and rvalue in modern C++ the same as in C++03?

Not exactly. The ideas are related, but modern C++ refines the model. In particular, modern rvalue includes both prvalue and xvalue.

Do I need to memorize all five categories to write C++?

Not immediately. Most developers mainly use lvalue, rvalue, and xvalue in practice. But knowing the full model helps when reading language rules or understanding templates and moves.

Can a const T& bind to an rvalue?

Yes. A const T& can bind to both prvalues and xvalues.

Why does std::move not always result in a move?

Because std::move only changes the value category. The actual move depends on whether the target operation has a move overload or move constructor available.

Take Quiz

Mini Project

Description

Build a small program that detects which overload is called for different expression categories. This helps you see how C++ treats named variables, literals, return values, and std::move(...) differently.

Goal

Create a program that prints whether each expression is treated as an lvalue or rvalue when passed to overloaded functions.

Requirements

  1. Create two overloaded functions: one taking int& and one taking int&&.
  2. Pass a named variable, an integer literal, and std::move(variable) to the overloads.
  3. Add a function that returns an int and pass its result to the overloads.
  4. Print clear output so the selected overload is easy to understand.
Take Quiz

Keep learning

Related questions

View more