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.
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);
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.
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.
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
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.
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 | + |
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 = ;
& a = x;
& b = ;
&& c = ;
&& d = std::(x);
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 includes both and .
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
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++ Casts Explained: C-Style Cast vs static_cast vs dynamic_cast
Learn the difference between C-style casts, static_cast, and dynamic_cast in C++ with clear examples, safety rules, and real usage tips.
C++ Lambda Expressions Explained: What They Are and When to Use Them
Learn what C++ lambda expressions are, why they exist, when to use them, and how they simplify callbacks, algorithms, and local logic.