Reference collapsing rules not applying as expected?

144 Views Asked by At

I am refreshing my memory on how perfect forwarding works in C++. I realize that a call to std::forward is forced to provide an explicit template parameter for a reason (i. e. when dealing with rvalue references that are actually lvalues), however when doing a sanity check on actual code, I was surprised by this (somewhat related) scenario:

#include <iostream>
#include <utility>
#include <type_traits>

template<class T>
T&& fwd(T& t) {
    return static_cast<T&&>(t);
}

template<class T>
T&& fwd(T&& t) {
    return static_cast<T&&>(t);
}

int main()
{
    int lnum = 3;
    if (std::is_rvalue_reference<decltype(fwd(lnum))>::value)
        std::cout << "It's rref." << std::endl;           // this get's printed on screen 
    else
        std::cout << "It's lref." << std::endl;

    return 0;
}

If I understand reference collapsing correctly (and I believe I do), type deduction should go like this:

int& && fwd(int& & t) {                        
    return static_cast<int& &&>(t);         
}

leading to

int& fwd(int& t) {                        
    return static_cast<int&>(t);         
}

Clearly that's not the case. What am I missing here?

2

There are 2 best solutions below

4
On BEST ANSWER

Actually, no referencing collapsing occurs. The relevant function template to pay attention to, i.e., the one selected, is:

template<class T>
T&& fwd(T& t) { // <-- not a forwarding reference
    return static_cast<T&&>(t);
}

Note that this function template has no forwarding references – the function parameter, t, is just an lvalue reference (T& t).

The T template parameter is deduced to int – not int& – because t is not a forwarding reference but just an lvalue reference. If you simply replace T by int in the function template above, then you will obtain:

template<class T>
int&& fwd(int& t) {
    return static_cast<int&&>(t);
}

No reference collapsing is applied as there is no such a thing here that would otherwise end up becoming a reference to a reference (e.g., int& && or int&& &&).

1
On

Firstly, the function that is called is T&& fwd(T& t). As such, there is no forwarding reference parameter. The parameter is an lvalue reference and the deduced T is int. As such, there are no references to collapse and the static cast produces int&&.

If the called function had been T&& fwd(T&& t) (i.e. if the better matching overload didn't exist), then your explanation of reference collapsing would be correct (except for the parameter which would be int& && which also collapses int&) and the return type would indeed be lvalue reference.