Understanding C++ Reference Collapsing Rules

Reference collapsing is a core language feature introduced in C++11 that enables perfect forwarding and template metaprogramming by resolving references to references. In C++, you cannot explicitly declare a reference to a reference in standard code, but such constructs frequently occur during template argument deduction, decltype evaluation, and alias expansion. This article explains the four reference collapsing combinations supported by C++ and how the compiler resolves them.

Why Reference Collapsing Occurs

When writing generic code, such as a function template taking a forwarding reference (T&&), substituting an argument deduced as a reference type creates an intermediate "reference to a reference." C++ resolves these instances using strict reference collapsing rules.

The Four Reference Collapsing Combinations

C++ supports two types of references: an lvalue reference (&) and an rvalue reference (&&). When combinations of these references are formed through type deduction or aliases, they collapse according to the following four rules:

  1. Lvalue reference to lvalue reference (& & \(\rightarrow\) &)
    If an lvalue reference to an lvalue reference is formed, it collapses into a standard lvalue reference.
    Example: If T is int&, then T& becomes int& &, which collapses to int&.

  2. Lvalue reference to rvalue reference (& && \(\rightarrow\) &)
    If an lvalue reference to an rvalue reference is formed, it collapses into an lvalue reference.
    Example: If T is int&, then T&& becomes int& &&, which collapses to int&.

  3. Rvalue reference to lvalue reference (&& & \(\rightarrow\) &)
    If an rvalue reference to an lvalue reference is formed, it collapses into an lvalue reference.
    Example: If T is int&&, then T& becomes int&& &, which collapses to int&.

  4. Rvalue reference to rvalue reference (&& && \(\rightarrow\) &&)
    If an rvalue reference to an rvalue reference is formed, it collapses into an rvalue reference.
    Example: If T is int&&, then T&& becomes int&& &&, which collapses to int&&.

The Core Rule

The four combinations can be summarized by a simple rule: if either reference is an lvalue reference, the result collapses to an lvalue reference (&). The result is an rvalue reference (&&) if and only if both references are rvalue references.

This behavior forms the backbone of std::forward, ensuring that the original value category (lvalue or rvalue) of an argument is preserved when passed through generic template wrappers.