Value Categories, Lvalue References, and Rvalue References
Deconstruct C++ value categories (lvalue, prvalue, xvalue), reference binding rules, lifetime extension of temporaries, and rvalue references (&&).
By the end, you’ll be able to…
- Classify expressions into primary value categories: glvalue, lvalue, rvalue, prvalue, and xvalue
- Compare lvalue references (T&) with const lvalue references (const T&) and lifetime extension rules
- Synthesize rvalue references (T&&) to enable move semantics and prevent expensive deep copying
01The Taxonomy of C++ Value Categories
In C++, every expression is characterized by two independent properties: Identity (does the program have a pointer or name to locate the object in memory?) and Moveability (can the object's resources be safely stolen or moved?). This gives rise to the complete value category taxonomy formalizing modern C++:
An lvalue has identity and cannot be moved (e.g., named variables like int x = 10;, function calls returning references, array index expressions). You can take the address of an lvalue via &x.
A prvalue ('pure rvalue') has no identity but can be moved (e.g., literal constants like 42, arithmetic temporaries like a + b, function calls returning values).
An xvalue ('eXpiring value') has identity and can be moved. It represents an object whose resources are near the end of their lifetime (e.g., the result of std::move(x)). Together, prvalues and xvalues form rvalues.
02Reference Binding Rules and Const Lifetime Extension
A standard non-const lvalue reference (T&) can ONLY bind to a modifiable lvalue. Attempting to write int& ref = 42; is a compilation error because a temporary literal has no permanent memory identity.
However, a const lvalue reference (const T&) possesses a unique superpower: it can bind to lvalues, const lvalues, and rvalues! When a const lvalue reference binds to a temporary prvalue (e.g., const std::string& s = get_name();), the C++ runtime extends the lifetime of the temporary object to match the scope of the reference itself.
An rvalue reference (T&&), introduced in C++11, can ONLY bind to rvalues (temporaries or xvalues). It signifies that the referenced object is ephemeral and its internal heap allocations or file descriptors can be pilfered without copying.
03Value Category Venn Diagram & Relationship
Understanding the relationships between the five value categories is essential for mastering template meta-programming and move semantics:
Notice that glvalue ('generalized lvalue') encompasses everything with identity, while rvalue encompasses everything that can be moved.
Expressions / \ glvalue rvalue / \ / \ lvalue xvalue prvalue[Named vars] [std::move] [Temporaries/42](Has Identity) (Has Identity) (No Identity)(Not Moveable) (Is Moveable) (Is Moveable)04Production Implications of References
In high-performance C++ systems, pass-by-value for large structs or containers incurs massive CPU performance penalties due to heap allocations in copy constructors.
Always pass large read-only parameters by const reference (const T&). Use rvalue references (T&&) in move constructors and move assignment operators to transfer ownership of resources in O(1) time without heap reallocations.
05Production Systems Architecture & Hardware Trade-Offs
From an operating system and hardware architecture perspective, systems software must account for physical execution realities that compiler abstractions intentionally hide. At the hardware level, instruction caches (I-Cache) and data caches (D-Cache) operate independently, and CPU pipeline front-ends rely heavily on branch target buffers (BTB) to minimize instruction fetch stalls. When code is organized into coherent, spatially contiguous blocks within translation units, binary code density increases, improving instruction cache hit rates across high-frequency execution loops.
Furthermore, modern linkers such as LLVM lld and mold perform Link-Time Optimization (LTO) across translation units. When LTO is enabled (-flto), the compiler writes LLVM bitcode into object files rather than final machine code, allowing the linker to perform cross-translation-unit function inlining, devirtualization, and dead-code stripping across the entire binary.
The ideas worth keeping.
- Expressions are partitioned into lvalues, prvalues, and xvalues based on identity and moveability.
- Pass large read-only objects by const lvalue reference (const T&) to eliminate copying.
- Rvalue references (T&&) enable O(1) resource transfer via move semantics.
Make it yours.
Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.
Exercise 1: Classify each of the following expressions in
Classify each of the following expressions into its precise value category (lvalue, prvalue, or xvalue): 1. int a = 5; 2. a + 10; 3. std::move(a); 4. ++a; 5. a++;
Explore a worked solution
// 1. 'a' -> lvalue (has identity, addressable)// 2. 'a + 10' -> prvalue (temporary result of arithmetic)// 3. 'std::move(a)' -> xvalue (cast to an expiring rvalue with identity)// 4. '++a' -> lvalue (pre-increment mutates 'a' in place and returns reference to 'a')// 5. 'a++' -> prvalue (post-increment creates and returns a temporary copy of previous value)Can you take the address of the expression using '&'?
Exercise 2: Explain why int ref 100 fails to compile
Explain why int& ref = 100; fails to compile, but const int& ref = 100; compiles successfully.
Explore a worked solution
// 100 is a prvalue literal with no identity or persistent memory address.// Non-const lvalue reference (int&) requires an addressable lvalue so changes can be stored.// const int& is explicitly allowed by the C++ standard to bind to temporaries,// extending the temporary's lifetime to match the lifetime of 'ref'.Recall the temporary lifetime extension rule for const references.
What stayed with you?
Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.
Exercise 1: Classify each of the following expressions in
Classify each of the following expressions into its precise value category (lvalue, prvalue, or xvalue): 1. int a = 5; 2. a + 10; 3. std::move(a); 4. ++a; 5. a++;
Build completed. Binary executed. [Exit code: 0]
Keep exploring.
The official references behind this lesson, when you want a second angle or the full details.
One lesson closer.
Reading is a start. Trying it yourself makes the connection.