We will check universal reference and then we will understand c++ reference collapsing. Universal References is a special concept. Here the reference occurs in two contexts. In both places, the type deduction is happens.
Function template parameters
template <typename T>
void func (T&& param);
In this example, func is a template function. On calling “func”, the passed argument type will deduce the type of template parameter T. The Argument param is of a special type (T&&). Here, when an && sign follows template type T. This is a universal reference.
Auto declarations with &&
auto&& var2 = var1;
The type of var1 deduces the data type of auto variable var2. The variable var2 is of type auto&& and here this is a Universal reference.
Universal References are not R-Values
Universal reference is not r-value reference. In Universal reference, the type of parameter is template type. However, in r-value reference, the exact data type is clear. Like, in following example, the type of param is int&&.
void func(int&& param);
int&& var2 = var1;
Universal reference has dual nature,
it can bind to either to L-value or R-value. (An R-value reference always bind to an R-value)
Once the type deduction happens with type of argument provided from calling function. The compiler then decides the status of universal references from type of argument. Means when program passes an L-value argument, then Universal reference will correspond to an L-value. Similarly when program passes an R-value argument, then template type corresponds to R-value type.
Reference Collapsing Rules
When the program passes an L-value or an R-value parameter to universal reference parameter, then to get final function signature, the compiler generates reference to reference expressions.
There are four possible reference-reference combinations
- lvalue to lvalue,
- lvalue to rvalue,
- rvalue to lvalue, and
- rvalue to rvalue
With these reference combinations, the compiler follows certain rules to collapse these references to references and generate them into a single reference. This is called c++ reference collapsing.
In simple terms, if either of the reference is an L-value then reference collapsing will lead to L-value finally. Otherwise, the reference collapsing will lead to R-value.
Reference Collapsing when program calls template function with L–value argument
template <typename T>
void func (T&& param); //Function template with Universal reference
int w;
func(w); //calling the template with an L-value
argument
Here, first T will deduce to type of passed argument. i.e., int&. (L-value)
This will create L-value reference to R-value reference in following statement:
func(int& && param)
As per rules, the final type will become L-value (from, L-value to R-value expression)
func(int & param);
Reference Collapsing when program calls template function with R–value argument
template <typename T>
void func (T&& param); //Function template with Universal reference
func(4) // calling the template with R-value
First, T will deduce to type of argument. i.e., int&& (R-value)
This will create R-value reference to R-value reference in following statement
func( int&& && param);
The final signature will become R-value (from R-value to R-value expression)
func(int&& param)
The underlying mechanism of these reference collapsing rules decides how std::forward does its work.
C++ Reference Collapsing happen in 4 contexts
- template instantiation
- auto type generation.
- The generation and use of typedefs
- Alias declarations
Above already explains scenarios, 1 and 2.
Typedef generation:
Compiler applies reference to reference collapsing rules when creating and evaluating typedef. The reference collapsing rules simply these reference and make a simple reference out of them.
For example, suppose we have a class template with an embedded typedef for && an rvalue reference type:
template <typename T>
class Widget
{
public:
typedef T&& typedef_rvalue_reference;
};
When an L-value instantiates the class.
Widget<int&> w;
The typedef_rvalue_reference will resolve to L-value
typedef int& && typedef_rvalue_reference;
finally, (L-value to R-value expression)
typedef int& typedef_rvalue_reference;