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.

🔑 Key line

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

  1. 01T&& is a forwarding reference, not an rvalue reference
  2. 02Reference collapsing: any & collapses the result to &
  3. 03Lvalue arg: T = int&, T&& collapses to int&
  4. 04Rvalue arg: T = int, T&& stays int&&
  5. 05Without std::forward: the rvalue becomes lvalue inside fwd()
  6. 06std::forward<T>(t) restores the original value category
  7. 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.

← Previous
Type Deduction: auto & Templates
Next →
Templates: SFINAE, enable_if, Type Traits, if constexpr, Concepts