Search code examples
c++c++11function-templatesforwarding-referencetemplate-argument-deduction

Function template deduction l-value reference and universal reference


Let's say I have the function copy:

template <typename Buf>
void copy(
    Buf&& input_buffer,
    Buf& output_buffer) 
{}

In which input_buffer is a universal reference and output_buffer is an l-value reference.

Reference collapsing rules make sure input_buffer is indeed, regardless of the deduced type of Buf, an universal reference and output_buffer is indeed an l-value reference.

However, I wonder how type Buf is deduced here.

I found out that copy is passed an r-value as input_buffer, (and an l-value as output_buffer, obviously) Buf is a non-reference type.

If I were to pass two l-values however, the program does not compile:

int i = 4;
int j = 6;

_copy(i, j);

I would expect the compiler to deduce Buf to int&. Following the reference collapsing rules, I would expect input_buffer to become an l-value reference, that is, & + && -> &, and output_buffer to become an l-value reference too; & + & -> &.

So the question is: Why doesn't this code compile?

(Note: I am not necessarily asking for a solution to the problem, but for an explanation.)

If I need to elaborate, feel free to ask.

EDIT: if call: copy(i, j); GNU GCC Compiler gives: error: no matching function for call to 'copy(int&, int&)' note: candidate: template void copy(Buf&&, buf&) note: template argument deduction/substitution failed: note: deduced conflicting types for parameter 'Buf' ('int&' and 'int')

if call: copy<int&>(i, j); OK.


Solution

  • a) Type deduction for forwarding reference:

    template<class T>
    void f(T&& val) {}
    

    [a.1] when you pass Lvalue T is deduced to be T&. So you have

    void f(T& && ){} -after reference collapsing-> void f(T&){}
    

    [a.2] when you pass Rvalue T is deduced to be T. So you have

    void f(T&& ) {}
    

    b) Type deduction for reference except forwarding reference:

    template<class T>
    void f(T& param){}
    

    when you pass Lvalue, T is deduced to be T. param has type T& but template argument is T, not T&.

    So below code compiles

    int i = 10;
    copy(20,i);
    

    because type deduction for first argument returns Buf==int since you passed 20 Rvalue. And result of deduction for second argument also returns Buf==int. So in both cases Buf is the same, code compiles.

    Code which doesn't compile:

    int i=1;
    int j=2;
    copy(i,j);
    

    What is deduced type for first argument? You are passing L-value, so Buf is int&. Second deduction returns Buf==int. These two deduced types are not the same, that is why code doesn't compile.