Modern (Effective) C++ - Understanding reference collapsing rules

This article is inspired from Item 28 of Effective Modern C++ by Scott Meyers.

Here we understand reference collapsing rules.

In the below function -

T  encodes (contains the necessary information) on whether the argument passed to param is Lvalue reference or Rvalue reference.

There are two rules to the encoding -
If the argument passed is Lvalue, then T is deduced as Lvalue reference.
If the argument passed is Rvalue, then T is deduced as Rvalue reference.

The above two rules is practically demonstrated with a program here -

In this program, we make use of boost type_index to print the type information. In the output, we can see that when x (Lvalue) is passed, T is deduced as int&. When 3 (Rvalue) is passed, T is deduced as int. This is the underlying mechanism of std::forward.

Before going into the workings of std::forward, it's first necessary to establish a important fact about references. Reference to reference is illegal in C++ ( auto& &rx = x ).

But when working with templates, references to references gets created during the process of template instantiation For example, in a function which uses universal references, if we pass an integer variable (Lvalue), it gets instantiated with

     void func(int& &&x)

But compiler later converts this to
 
     void func(int& x)

This conversion process is called reference collapsing.

There are four combinations of refrence to reference combinations. Lvalue to Lvalue, Lvalue to Rvalue, Rvalue to Lvalue, Rvalue to Rvalue.

The rule which dictates the reference collapsing process is
If either of the value is Lvalue, then T is deduced to Lvalue reference. If both are Rvalues, then T is deduced to Rvalue reference.

Now we come back to the discussion of std::forward.
In the previous article, we saw that std::forward<T> is applied on universal references. We have also learnt that T encodes i.e contains the information, on whether it is Rvalue reference or Lvalue reference.

std::forward<T> will hence cast the argument as Rvalue only if T is encoded as Rvalue. Otherwise we will get a Lvalue. Let's see how it works now. std::forward is implemented as


In case where int variable(Lvalue) is passed, std::forward will be instantiated as -

In case where a Rvalue like number 3 is passed, std::forward will be instantiated as -


The reference collapsing rules is almost the same when using auto as well, as demonstrated in the below program -


The other two contexts when reference collapsing occurs is during typedefs and decltype.


Modern (Effective) C++ - Understanding reference collapsing rules  Modern (Effective) C++ - Understanding reference collapsing rules Reviewed by zeroingTheDot on February 23, 2018 Rating: 5

1 comment:

  1. "If the argument passed is Rvalue, then T is deduced as Rvalue reference."
    This is incorrect.
    Can you explain if it deduces to Rvalue, why does it print "int" in your example

    ReplyDelete

Powered by Blogger.