Search Knowledge

© 2026 LIBREUNI PROJECT

Modern C++ Programming / Foundations

References and Value Categories

References: Aliases with Semantics

References are aliases to existing objects, unlike pointers which are objects themselves. C++ provides two kinds: lvalue references (T&) and rvalue references (T&&, C++11).

int x = 10;
int& ref = x;   // Lvalue reference to x
ref = 20;       // Modifies x

int* ptr = &x;  // Pointer is a separate object
*ptr = 30;      // Modifies x through pointer

Key Properties of References

  1. Must be initialized: Unlike pointers, references cannot be null or uninitialized
  2. Cannot be rebound: Once bound, a reference always refers to the same object
  3. No separate storage: References typically compile to pointers but have different semantics
  4. No reference to reference: int& & is illegal (but see reference collapsing)
int a = 5;
int& r1 = a;
// int& r2;     // Error: must be initialized
// r1 = b;      // This assigns b's value to a, doesn't rebind r1

Value Categories: lvalues and rvalues

Every C++ expression belongs to a value category. The fundamental distinction:

  • lvalue: Has persistent identity (name, memory address)
  • rvalue: Temporary, about to be destroyed
int x = 10;    // x is an lvalue
int* p = &x;   // OK: can take address of lvalue

// int* p = &10;  // Error: can't take address of rvalue
// int& r = 42;   // Error: lvalue ref can't bind to rvalue

C++11 introduced finer categories:

System Diagram
expressionglvaluervaluelvalueprvaluexvalueHas identitye.g., variable namesPure rvaluee.g., literals, temporarieseXpiring valuee.g., std::move result

Rvalue References and Move Semantics

Rvalue references (T&&) bind to temporaries, enabling move semantics: transferring resources instead of copying.

std::string s1 = "hello";
std::string s2 = s1;              // Copy: s1 still valid
std::string s3 = std::move(s1);   // Move: s1 left in valid but unspecified state

void process(std::string&& s) {
    // s is an rvalue reference (but s itself is an lvalue!)
}

process(std::string("temp"));     // OK: binds to temporary
process(std::move(s2));           // OK: std::move casts to rvalue
// process(s2);                   // Error: lvalue can't bind to &&

The lvalue-rvalue Paradox

Inside a function, an rvalue reference parameter is itself an lvalue (it has a name):

void func(std::string&& s) {
    std::string copy = s;            // Copy, not move (s is lvalue)
    std::string moved = std::move(s); // Move (explicitly cast to rvalue)
}

Const lvalue References: Universal Binding

A const T& can bind to both lvalues and rvalues, making it perfect for read-only parameters:

void print(const std::string& s) {  // Accepts anything
    std::cout << s;
}

std::string str = "hello";
print(str);              // OK: binds to lvalue
print("world");          // OK: binds to temporary
print(str + " world");   // OK: binds to temporary result

This is why pre-C++11 code used const T& everywhere: it’s the only reference type that accepts both lvalues and rvalues.

Reference Collapsing Rules

When references are formed through templates or typedef, C++ applies reference collapsing:

T& &   → T&
T& &&  → T&
T&& &  → T&
T&& && → T&&

Rule: An rvalue reference to an rvalue reference becomes rvalue reference; everything else becomes lvalue reference.

template<typename T>
void wrapper(T&& arg) {  // Forwarding reference (see next section)
    // If T is int&, then T&& becomes int& (reference collapsing)
    // If T is int, then T&& is int&&
}

Forwarding References (Universal References)

In template contexts, T&& has special meaning—it’s a forwarding reference that can bind to anything:

template<typename T>
void forward_wrapper(T&& arg) {  // Forwarding reference
    target(std::forward<T>(arg));  // Perfect forwarding
}

int x = 10;
forward_wrapper(x);        // T = int&, arg is int&
forward_wrapper(10);       // T = int, arg is int&&

std::forward<T> preserves the value category: forwards lvalues as lvalues, rvalues as rvalues.

Interactive Lab

Reference Semantics

int x = 5;
int& r = x;
r = 10;
std::cout << ;  // Outputs 10
Runtime Environment

Interactive Lab

1#include <iostream>
2#include <utility>
3 
4void process(int& x) { std::cout << "lvalue\n"; }
5void process(int&& x) { std::cout << "rvalue\n"; }
6 
7int main() {
8 int a = 10;
9 process(a); // Calls process(int&)
10 process(42); // Calls process(int&&)
11 process(std::move(a)); // Calls process(int&&)
12 return 0;
13}
System Console

Waiting for signal...