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:
Lvalue reference to lvalue reference (
& &\(\rightarrow\)&)
If an lvalue reference to an lvalue reference is formed, it collapses into a standard lvalue reference.
Example: IfTisint&, thenT&becomesint& &, which collapses toint&.Lvalue reference to rvalue reference (
& &&\(\rightarrow\)&)
If an lvalue reference to an rvalue reference is formed, it collapses into an lvalue reference.
Example: IfTisint&, thenT&&becomesint& &&, which collapses toint&.Rvalue reference to lvalue reference (
&& &\(\rightarrow\)&)
If an rvalue reference to an lvalue reference is formed, it collapses into an lvalue reference.
Example: IfTisint&&, thenT&becomesint&& &, which collapses toint&.Rvalue reference to rvalue reference (
&& &&\(\rightarrow\)&&)
If an rvalue reference to an rvalue reference is formed, it collapses into an rvalue reference.
Example: IfTisint&&, thenT&&becomesint&& &&, which collapses toint&&.
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.