c++c++11templatesperfect-forwardingreference-collapsing

Reference collapsing rules not applying as expected?


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?


Solution

  • 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&& &&).