C++ - Value Categories & Initialization
Overview
Estimated time: 70–90 minutes
Master value categories and initialization forms to reason about overloads, moves, and perfect forwarding. Learn how reference collapsing and deduction affect your APIs.
Learning Objectives
- Differentiate lvalue, xvalue, prvalue and how they bind to references.
- Understand direct/value/list/aggregate initialization and brace elision rules.
- Apply reference-collapsing and forwarding rules correctly.
Prerequisites
Value categories
#include
void f(int&){ std::cout << "lvalue\n"; }
void f(const int&){ std::cout << "const lvalue\n"; }
void f(int&&){ std::cout << "rvalue\n"; }
int main(){ int x=0; f(x); f(1); }
Expected Output:
lvalue
rvalue
Initialization forms
struct S{ int a; double b; };
S s1{1,2.0}; // list init (aggregate)
S s2 = {1,2.0}; // copy list init
S s3(1,2.0); // direct init (since aggregate, not viable pre C++20)
Reference collapsing
template
void g(T&& t){ auto&& u = static_cast(t); (void)u; }
// T& & -> T&
// T&& & -> T&
// T& && -> T&
// T&& && -> T&&
Common Pitfalls
- Assuming prvalues always create temporaries—C++17+ can materialize differently; rely on rules, not guesses.
- Using list-init where narrowing conversions are disallowed; be explicit if you need narrowing.
Checks for Understanding
- Which references can bind to rvalues?
- What does list initialization prevent by default?
Show answers
- Only rvalue references (T&&) or const lvalue references (const T&).
- Narrowing conversions.
Exercises
- Write overloads to distinguish lvalue vs rvalue strings and log which one was called.
- Experiment with aggregate vs non-aggregate initialization and list-init narrowing.