Question
C++ Value Categories Explained: lvalues, rvalues, xvalues, glvalues, and prvalues
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:
lvaluervaluexvalueglvalueprvalue
This raises a few important questions:
- What does each of these expression categories mean?
- How do the newer categories relate to the older
lvalueandrvaluemodel from C++03? - Do
lvalueandrvaluemean 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:
lvaluexvalue
A glvalue is basically an expression that identifies an object or function.
rvalue
In modern C++, rvalue is a broader category that includes:
prvaluexvalue
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=lvalueorxvaluervalue=prvalueorxvalue
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&, orT&&, - 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.
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
xis an lvalue because it names an object.2is a prvalue.std::move(x)is an xvalue.- Both
2andstd::move(x)can bind toint&&, 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:
-
int x = 10;xis a named object.- The expression
xis an lvalue.
-
inspect(x);- Overload resolution checks both functions.
inspect(int&)matches lvalues.inspect(int&&)does not bind to an lvalue.- Output:
lvalue
-
inspect(10);10is a literal.- It is a prvalue.
- A prvalue is an rvalue.
- It binds to
int&&. - Output:
rvalue
-
inspect(std::move(x));std::move(x)convertsxinto 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.
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
| Category | Has identity? | Can appear as movable temporary? | Includes |
|---|---|---|---|
| lvalue | Yes | No | named objects, functions |
| prvalue | Usually no direct reusable identity in the old sense | Yes | literals, computed results, temporaries |
| xvalue | Yes | Yes | std::move(x), certain returned objects |
| glvalue | Yes | Sometimes | lvalue + xvalue |
| rvalue | Not necessarily identity-based | Yes | prvalue + xvalue |
Old vs modern model
| C++03 idea | Modern C++ equivalent |
|---|---|
| lvalue | still exists, but now part of glvalue |
| rvalue | split more precisely into prvalue and xvalue |
Reference binding comparison
| Reference type | Binds to lvalue? | Binds to prvalue? | Binds to xvalue? |
|---|---|---|---|
T& | Yes | No | No |
const T& | Yes | Yes | Yes |
T&& | No | Yes | Yes |
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)
Cheat Sheet
Quick rules
lvalue= named object or function, has identityprvalue= pure temporary value or computed resultxvalue= expiring value, usually produced bystd::moveglvalue=lvalueorxvaluervalue=prvalueorxvalue
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.
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
- Create two overloaded functions: one taking
int&and one takingint&&. - Pass a named variable, an integer literal, and
std::move(variable)to the overloads. - Add a function that returns an
intand pass its result to the overloads. - Print clear output so the selected overload is easy to understand.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.