Search code examples
c++templatesrvalue-reference

How does C++ make template deduction on rvalue reference?


template<typename T>
void foo(T&& arg);

I know if arg is an lvalue, e.g. int x = 0; foo(x); then T = int& and the function will be foo(int& &&), which is foo(int&).

if arg is an rvalue, e.g. foo(0); then T = int and the function will be foo(int&&).

What if I have

template<typename T>
void foo(T& arg);

template<typename U>
void bar(U&& u)
{
    foo(u);
}

What would be T in foo when calling bar(0)?


Solution

  • template<typename U>
    void bar(U&& u)
    {
        foo(u);
    }
    

    Regardless of what you pass in to bar, u is an lvalue because it has a name.

    u may be an lvalue reference or an rvalue reference, but when you pass it to foo, its "reference-ness" is ignored, as per usual.

    Forget about lvalues and rvalues and templates for a moment. A reference is supposed to be an alias for another variable, and refering to a reference by name is supposed to behave, in most cases, just as if you were refering to the original variable:

    int i = 42;
    int& r = i;
    
    f(int);
    f(int&);
    f(i); // error: call to ambiguous overload
    f(r); // error: call to ambiguous overload
    
    g(int);
    g(r); // OK, pass by copy, reference-ness of r is irrelevant
    
    h(int&);
    h(i); // OK, pass by reference, non-reference-ness of i is irrelevant
    

    In the function call statements above, the id-expressions i and r are lvalues of type int. The fact that the variables i and r are a non-reference and a reference respectively has no bearing on the type or value category of the corresponding id-expressions. This is the way references have always worked, and rvalue references don't change that.

    template<typename T>
    void foo(T& arg);
    

    There is nothing you could pass to foo that would make T be a reference type. arg will always be an lvalue reference.

    If you want to propagate the value category of an argument you need std::forward:

    template<typename U>
    void baz(U&& u)
    {
        foo(std::forward<U>(u));
    }
    
    baz(42); // error: attempted to call foo() with an rvalue