Function template overloading with reference collapsing

Viewed 69

In addition to this question, Function template deduction l-value reference and universal reference, I have another question. Note you do not have to read this post to understand this, but it might be helpful. This post does by the way not show the ideal solution to the problem I believe, but that's not the point.

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

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

...

int i = 4;
int j = 6;

copy<int&>(i, j); // copy taking two l-values; calls second instance.

I would expect if I were to call copy<int&>(i,j), the first instance of copy would be called. Type Buf is specified, so does not require to be deduced. Reference collision rules result in both input_buffer and output_buffer being l-value references. The first instance is the first valid function.

It is not a template specialization, so it does also surprise me that the compiler actually chooses either, in stead of giving an error.

The second instance of copy may appear more specified, but if Buf is int& the first instance also expects two l-value references.

So, the question is, why is the second instance preferred? I would expect that the compiler first actually creates the function, by substituting the template argument. Then, it turns out that Buf&& input_buffer in instance one is equal to Buf&.

Feel free to ask if I need to elaborate.

1 Answers

Both templates (1) and (2) instantiate to the same signature, so which one is called depends on the partial ordering rules. Partial ordering operates on the template declarations, not on their specializations produced by implicit instantiation nor on their explicit specializations.

According to the partial ordering rules, we determine whether (1) is at least as general as (2), and whether (2) is at least as general as (1). If the answers are respectively "yes" and "no", then (2) is more specialized and is chosen.

To determine whether (1) is at least as general as (2), we synthesize some unique type for Buf and substitute it into the signature of (2), then try to deduce the arguments of (1) from the hypothetical specialization so generated. The idea is that if this works for a unique type, then it must also work for every other type.

Indeed, if we substitute Buf = Unique in (2), we get

void copy (Unique& input_buffer, Unique& output_buffer)

This results in Buf being deduced as Unique& in (1) (this works because of the reference-collapsing rules).

If we do it the other way around, substituting Buf = Unique into (1), this results in the signature

void copy (Unique&& input_buffer, Unique& output_buffer)

Now we try to deduce Buf in (2) from this---but of course it doesn't work, because (2) can only ever generate specializations where both parameters are lvalue references.

Related