[C++] Values (lvalue / rvalue / xvalue / prvalue / glvalue)
Abstract
Click Run ▶
In the above code, the compiler fails to compile and returns an error: cannot bind non-const lvalue reference of type ‘int&’ to an rvalue of type ‘int’. In C/C++, the initial value of a reference to non-const must be an lvalue. This post provides these value categories for understanding C++’s complicated value semantics.
Summary
Every C++ expression has a type and a value category.
- Primary category:
lvalue,xvalue,prvalue. - Mixed category:
glvalue= lvalue ∪ xvalue,rvalue= prvalue ∪ xvalue.
These categories steer overload resolution, move semantics, and lifetime.
two-axis classification
Identity(designates a specific storage location): yes →glvalue(lvalue/xvalue)Move-eligible: yes →rvalue(prvalue/xvalue)
1. Definition
In C++, every expression has a value category:
| Classification | Move-eligible | Not Move-eligible |
|---|---|---|
| Has identity | xvalue | lvalue |
| No identity | prvalue | - |
Identity means the expression designates a specific storage location (it has a stable address/identity).
Value categories are also grouped into composite sets and primary categories:
- Mixed category:
glvalue: expressions with identity.rvalue: expressions treated as move-eligible.
- Primary category:
lvalue: has identity; usually addressable; not treated as move-eligible by default.xvalue: has identity; expiring (resource-reusable); move-eligible.prvalue: no identity; denotes a result value/object (since C++17).
1.1. lvalue
An lvalue is a glvalue that is not an xvalue—i.e., an expression with identity (designates a specific object, bit-field, or function) that is not treated as move-eligible in overload resolution.
Click Run ▶
Common sources of lvalues:
- Named objects (
x), dereference (*p), subscript (a[i]), member access on an lvalue base (obj.m), bit-fields - Function designators (e.g.,
fused for a call) - Result of pre-increment/decrement
- String literals (
"text") —const char[N]array lvalues
What lvalues can do (with caveats):
- Appear as the left operand of built-in assignment if they are modifiable lvalues (not
const, not a function, bit-fields have restrictions). - Have their address taken with built-in
&(bit-fields cannot; user-definedoperator&may affect this). - Often denote entities whose lifetime outlives the expression; however, an
lvaluecan also designate a subobject of a temporary, whose lifetime ends at the end of the full-expression.
1.2. xvalue
An xvalue is a glvalue that denotes an “expiring” object or subobject: it has identity (an addressable storage location) and is treated as move-eligible in overload resolution (binds to T&&). Typical producers are std::move(x), static_cast<T&&>(x), and calls that return T&&. Member access on a class rvalue also yields xvalues (C++17: via temporary materialization).
Click Run ▶
Common sources of xvalues:
std::move(x),static_cast<T&&>(x)- Function calls that return
T&& - Member access on a class rvalue (e.g.,
S{}.m,std::move(obj).m) — C++17 temporary materialization
What xvalues can do (with caveats):
- Bind to
T&&(and toconst T&), not to non-constT&. - Dispatch to
&&-qualified member functions (objas rvalue). - Are glvalues: they have identity (you’re still talking about that object).
- Cannot be the left operand of built-in assignment (needs a modifiable lvalue).
- Built-in
&(address-of) is not applicable to rvalues (including xvalues). (A class may overloadoperator&, but that’s separate from the built-in rule.)
Lifetime notes (important):
std::move(x)produces an xvalue referring toxitself.xdoes not vanish; it stays alive but may be in a moved-from state (valid but unspecified).- When a class prvalue is used where a glvalue is required, temporary materialization creates a temporary object and the resulting expression is an xvalue designating that temporary; that temporary dies at the end of the full-expression.
1.3. prvalue
A prvalue (pure rvalue) is an expression with no identity: it doesn’t designate a specific storage location. It is an rvalue, so it binds to T&& and const T&, but not to non-const T&.
Built-in address-of & does not apply to a prvalue.
Since C++17, class/array prvalues denote a result object; when a glvalue is required (e.g., binding to a reference, member access), a temporary materialization conversion creates a temporary and the expression yields an xvalue designating that temporary.
Click Run ▶
Common sources of prvalues
- Non-string literals:
42,3.14,true,'c',nullptr(string literals are array lvalues) - Arithmetic / relational / logical / bitwise results:
a + b,a < b,x && y,x | y, … - Post-increment/decrement:
x++,x--(pre-inc/dec++x/--xare lvalues) - By-value function calls:
T f(); f() - Temporary objects:
T{...},T(...),S{}(class prvalue; member access yields xvalue) - Lambda expressions:
[&]{ return 0; }(closure object prvalue) - Many conditional expressions
cond ? e1 : e2(when they don’t meet lvalue rules)
What prvalues can do (with caveats)
- Bind to
T&&andconst T&; cannot bind to non-constT&. - Appear on the right-hand side of assignment; initialize objects/parameters.
- Built-in
&is not allowed on prvalues (&(a+b)ill-formed). - Member access on a class prvalue is allowed since C++17 and yields an xvalue (materialized temporary).
Lifetime notes
- A scalar prvalue (e.g.,
a + b) has no identity; when used to initialize a variable, that value is copied/moved into the target. - A class prvalue denotes a result object; upon materialization (e.g., binding to
const T&), a temporary is created and destroyed at the end of the full-expression (lifetime can be extended by binding to aconst T&orT&&in certain cases).
2. Common Expressions
| Expression | Type (ex.) | Category | Notes |
|---|---|---|---|
x, *p, a[i], obj.m | as declared | lvalue | Has identity; &x OK. |
"hello" | const char[N] | lvalue | String literal is an array lvalue. |
Function name f | function type | lvalue | Function designators are lvalues. |
std::move(x) static_cast<T&&>(x) | T&& | xvalue | Move-eligible glvalue. |
Call returning T&& | T&& | xvalue | Rvalue-ref result. |
42, 3.14, a+b, T{...}, f() | int, double, T | prvalue | Class/array prvalues denote result objects (C++17). |
static_cast<T&>(e) | T& | lvalue | Cast to lvalue ref. |
static_cast<void>(e) throw e | void | prvalue | Discarded-value contexts. |
a.mf / p->mf (non-static mem-func) | function type | prvalue | Only as left operand of () call. |
3. Quick Decision Guide
- Identity?
- Yes → it’s a glvalue (either lvalue or xvalue) → go to (2).
- No → prvalue.
- Move-eligible? (would it bind to
T&&?)- Yes → xvalue.
- No → lvalue.
- Member access: follows the base expression’s category.
- lvalue base → lvalue member (
obj.m) - xvalue or class prvalue base → xvalue member (
std::move(obj).m,S{}.m)
- lvalue base → lvalue member (
- Inc/dec:
++x/--x→ lvalue.x++/x--→ prvalue.
Address-of: built-in
&applies only to lvalues (and functions).- Overload binding:
T&← lvalueconst T&← lvalue or rvalueT&&← rvalue (prvalue/xvalue)
4. Reference Binding Cheat Sheet
| Target parameter | Binds to lvalue | Binds to prvalue/xvalue | Notes |
|---|---|---|---|
T& | O | X | For modifiable lvalues only |
const T& | O | O | Materializes temporaries; extends lifetime |
T&& | X | O | Consuming/move sinks |
T&& (forwarding ref in templates) | O | O | Use std::forward<T>(t) to preserve category |
Tip: To classify an expression at compile time, use
decltype((expr)):
lvalue →T&, xvalue →T&&, prvalue →T(non-reference).
5. Materialization & Elision (C++17+)
- Temporary materialization
- when a class/array prvalue appears where a glvalue is required (e.g., reference binding, member access), the implementation materializes a temporary and the resulting expression is an xvalue designating that temporary.
- Examples:
S{}.m,const S& r = S{};
- Lifetime extension: if a temporary binds to
const T&orT&&, its lifetime is extended appropriately. - Mandatory copy elision: in many cases (e.g.,
T x = T{};,return T{...};,throw T{...};) the object is constructed directly in place with no separate temporary.
6. Examples
1
2
3
4
5
6
7
8
#include <string>
#include <utility>
struct S { int m = 1; void touch() & {} void touch() && {} };
S s; // 's' expression: lvalue
(std::move(s)).touch(); // rvalue base -> calls &&-qualified member
auto t = S{}; // constructed directly from a prvalue (elision)
static_assert(std::is_rvalue_reference_v<decltype((S{}.m))>); // member access: xvalue
1
2
3
4
int x = 0;
static_assert( std::is_lvalue_reference_v<decltype((x))> ); // lvalue
static_assert(!std::is_lvalue_reference_v<decltype((x++))>); // prvalue
static_assert( std::is_rvalue_reference_v<decltype((std::move(x)))> ); // xvalue
7. Pitfalls
- Rvalue-ref variable name is an lvalue:
int&& rr=0; rris an lvalue—usestd::move(rr)to pass as rvalue. - Built-in
&on rvalues:&std::move(x)and&(a+b)are ill-formed. - Temporary lifetime mistakes:
const T& r = T{};extends lifetime; views/subobjects may not. - Overusing
std::move: can block copy elision (return x;is often best post-C++17). - Bit-fields: address cannot be taken; non-const lvalue refs cannot bind.
- Member function designator misuse:
obj.mf(the designator) is only for calling.
8. Since / Changed in
- C++17: prvalues denote result objects; temporary materialization (prvalue→xvalue); wider mandatory copy elision.
- C++23: clarifications: certain move-eligible expressions (e.g., some
return/co_return/throwsites) are treated as rvalues for overload resolution.
9. See also
- Value categories
- Implicit conversions (temporary materialization)
- Reference initialization
- Order of evaluation.