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

  1. Which references can bind to rvalues?
  2. What does list initialization prevent by default?
Show answers
  1. Only rvalue references (T&&) or const lvalue references (const T&).
  2. Narrowing conversions.

Exercises

  1. Write overloads to distinguish lvalue vs rvalue strings and log which one was called.
  2. Experiment with aggregate vs non-aggregate initialization and list-init narrowing.