cpp core · advanced
Perfect Forwarding & Reference Collapsing
A forwarding reference (template<typename T> void f(T&&)) differs from an rvalue reference (void g(int&&)) because T is deduced: T=int& for lvalue arguments and T=int for rvalue arguments. Reference collapsing then simplifies T&&: any combination with a single & collapses to an lvalue reference; only && && stays an rvalue reference. Without std::forward, the parameter t has a name and is always an lvalue inside the function — the caller's rvalue intent is silently discarded. std::forward<T>(t) is static_cast<T&&>(t): when T=int it casts to int&& (rvalue), when T=int& it casts to int& (lvalue), perfectly preserving what the caller signalled. Use std::move when you unconditionally want to give up ownership; use std::forward in templates to pass through.
In a template, T&& is a forwarding reference that deduces T as T& for lvalues and T for rvalues; reference collapsing reduces T& && to T& and leaves T&& alone; std::forward<T>(t) = static_cast<T&&>(t) restores the original value category so inner() can move when the caller passed an rvalue.
The code
// T&& in a template = forwarding reference (binds lvalue OR rvalue)template <typename T>void fwd(T&& t) { inner(std::forward<T>(t)); // preserves value category}
// Reference collapsing — the only rule you need:// T& & -> T& T& && -> T& T&& & -> T& T&& && -> T&&// Rule: any & collapses to &; only && && stays &&
int x = 5;fwd(x); // lvalue: T=int&, t is int& && -> int& (lvalue ref)fwd(42); // rvalue: T=int, t is int&& -> int&& (rvalue ref)
// WITHOUT std::forward: t has a name -> always lvalue inside fwd()// inner(t) -> inner sees lvalue even when caller passed rvalue (BUG)// WITH std::forward<T>(t): static_cast<T&&>(t)// T=int& -> cast to int& (lvalue) T=int -> cast to int&& (rvalue) (CORRECT)What this lesson walks through
- 01T&& is a forwarding reference, not an rvalue reference
- 02Reference collapsing: any & collapses the result to &
- 03Lvalue arg: T = int&, T&& collapses to int&
- 04Rvalue arg: T = int, T&& stays int&&
- 05Without std::forward: the rvalue becomes lvalue inside fwd()
- 06std::forward<T>(t) restores the original value category
- 07std::forward preserves; std::move unconditionally casts
When you write T&& inside a template (where T is deduced), it is a forwarding reference: it binds to both lvalues and rvalues. This is completely different from a concrete int&& which only binds rvalues. The distinction is the deduced parameter T.
See it animated — step by step, at your own pace
Unlock the full interactive walkthrough of Perfect Forwarding & Reference Collapsing and 100+ animated C++ interview lessons.