Search code examples
c++c++11templatesoverloadingforwarding-reference

Why does it not work when an lvalue-ref-arg and an rvalue-ref-arg are passed as the same forwarding-ref type?


#include <type_traits>

template<typename T>
void f(T&& a, T&& b)
{}

int main()
{
    int n;
    f(n, std::move(n));
}

T&& is a forwarding reference type, so I think decltype(a) should be int& and decltype(b) should be int&&.

However, the code above generates the following error:

main.cpp(13,2): error : no matching function for call to 'f' f(n, std::move(n));

main.cpp(7,6): note: candidate template ignored: deduced conflicting types for parameter 'T' ('int &' vs. 'int')

void f(T&& a, T&& b)

1 error generated.

Why does it not work when an lvalue-ref-arg and an rvalue-ref-arg are passed as the same forwarding-ref type?

My compiler is clang 4.0.


Solution

  • The compiler error is pretty straight forward, T cannot be deduced in int& and int at the same time. In order for this to work you would have to provide an additional template argument:

    template<typename T1, typename T2>
    void f(T1&& a, T2&& b)
    {}
    

    Reasoning

    Template argument deduction follows special rules when dealing with forwarding references (a.k.a Universal references) which are based on reference collapsing rules

    • U& & becomes U&
    • U& && becomes U&
    • U&& & becomes U&
    • U&& && becomes U&&

    for example if you have a function template:

    template<typename T>
    void foo(T&&) {}
    
    1. If foo input is an lvalue of type U, T will be deducted to U&. Following the reference collapsing rules above the argument type will become U&.
    2. If foo input is a rvalue of type U, T will be deducted to U and consequently the argument type will be U&&.

    Now following the reasoning above, template argument deduction will deduce T into int& for your first argument since input is an lvalue.

    Template argument deduction will try to match the type of the second argument but for the second argument since input is an rvalue following the rules above T will be deduced to int.

    At this point the compiler throws its hands up in the air and screams "dude T's deducted type must match for all input arguments".