Post

[C++] Values (lvalue / rvalue / xvalue / prvalue / glvalue)

[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:

ClassificationMove-eligibleNot Move-eligible
Has identityxvaluelvalue
No identityprvalue-

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., f used 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-defined operator& may affect this).
  • Often denote entities whose lifetime outlives the expression; however, an lvalue can 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 to const T&), not to non-const T&.
  • Dispatch to &&-qualified member functions (obj as 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 overload operator&, but that’s separate from the built-in rule.)

Lifetime notes (important):

  • std::move(x) produces an xvalue referring to x itself. x does 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/--x are 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&& and const T&; cannot bind to non-const T&.
  • 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 a const T& or T&& in certain cases).

2. Common Expressions

ExpressionType (ex.)CategoryNotes
x, *p, a[i], obj.mas declaredlvalueHas identity; &x OK.
"hello"const char[N]lvalueString literal is an array lvalue.
Function name ffunction typelvalueFunction designators are lvalues.
std::move(x)
static_cast<T&&>(x)
T&&xvalueMove-eligible glvalue.
Call returning T&&T&&xvalueRvalue-ref result.
42, 3.14, a+b,
T{...}, f()
int, double, TprvalueClass/array prvalues denote result objects (C++17).
static_cast<T&>(e)T&lvalueCast to lvalue ref.
static_cast<void>(e)
throw e
voidprvalueDiscarded-value contexts.
a.mf / p->mf
(non-static mem-func)
function typeprvalueOnly as left operand of () call.

3. Quick Decision Guide

  1. Identity?
    • Yes → it’s a glvalue (either lvalue or xvalue) → go to (2).
    • No → prvalue.
  2. Move-eligible? (would it bind to T&&?)
    • Yes → xvalue.
    • No → lvalue.
  3. 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)
  4. Inc/dec:
    • ++x/--xlvalue.
    • x++/x--prvalue.
  5. Address-of: built-in & applies only to lvalues (and functions).

  6. Overload binding:
    • T& ← lvalue
    • const T& ← lvalue or rvalue
    • T&& ← rvalue (prvalue/xvalue)

4. Reference Binding Cheat Sheet

Target parameterBinds to lvalueBinds to prvalue/xvalueNotes
T&OXFor modifiable lvalues only
const T&OOMaterializes temporaries; extends lifetime
T&&XOConsuming/move sinks
T&&
(forwarding ref in templates)
OOUse 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& or T&&, 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; rr is an lvalue—use std::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/throw sites) are treated as rvalues for overload resolution.

9. See also

  • Value categories
  • Implicit conversions (temporary materialization)
  • Reference initialization
  • Order of evaluation.

10. References

This post is licensed under CC BY 4.0 by the author.