2
votes

Partial ordering of function templates containing template parameter packs is independent of the number of deduced arguments for those template parameter packs.

template<class...> struct Tuple { };
template<          class... Types> void g(Tuple<Types ...>);        // #1
template<class T1, class... Types> void g(Tuple<T1, Types ...>);    // #2
template<class T1, class... Types> void g(Tuple<T1, Types& ...>);   // #3

g(Tuple<>());                     // calls #1
g(Tuple<int, float>());           // calls #2
g(Tuple<int, float&>());          // calls #3
g(Tuple<int>());                  // calls #3

The above is quoted from partial ordering of overload function templates. I don't quite understand why g(Tuple<int>()); // calls #3. Specifically, why can't #2 be called? The following are my reasoning, please point out any mistakes :

Note: I will ignore #1 b/c it is explained well here

Step 1: Deduction and substitution and overload resolution come up with these:

  • void(Tuple< int>) [T1 = int, Types is empty] #2
  • void(Tuple< int>) [T1 = int, Types& is empty] #3

Step 2: Transform both function templates:

  • void g(Tuple< C1, Pack1...>);
  • void g(Tuple< C2, Pack2&...>);

Step3: This is a function call context, the types are those function parameter types for which the function call has arguments:

  • Deduce Tuple< T1, Types...> from Tuple< C2, Pack&...> OK [T1 = C2; Types... = Pack&...]
  • Deduce Tuple< T1, Types&...> from Tuple< C1, Pack1...>) OK? [T1 = C1; what about Types&...? Can it be deduced from Pack1...? Are reference dropped here?]

3) If P is a reference type, the type referred to by P is used for deduction.This seems fine.

If P has one of the forms that include a template parameter list < T> or < I>, then each element Pi of that template argument list is matched against the corresponding template argument Ai of its A. If the last Pi is a pack expansion, then its pattern is compared against each remaining argument in the template argument list of A. A trailing parameter pack that is not otherwise deduced, is deduced to an empty parameter pack.

Step4:If the last step is correct. Then it means #3 is not more specialized than #2. So it is ambiguous as to which function template should be resolved to.

Update: I think I misunderstood the relevant quotes above. When we match up template parameters in P with template arguments in A, they are matched up verbatim, which means that all the transforms and analysis done on function call parameters and arguments do not apply again when we match up template parameters/arguments in P/A(actually function call parameter/arguments). Then it will fail in Step 3 above to deduce Tuple< T1, Types&...> from Tuple< C1, Pack1...>). So #3 is more specialized.

1
Did you try with the latest versions of various compilers? – Walter
I tried on g++ only. Where can I try other compilers online? – Rich
have a look at melpon.org/wandbox – m.s.

1 Answers

3
votes

First step in overload resolution is finding all the candidates. For the call g(Tuple<int>()), there are three viable candidates:

g(Tuple<int>); // #1: [Types = {int}]
g(Tuple<int>); // #2: [T1 = int, Types = {}]
g(Tuple<int>); // #3: [T1 = int, Types = {}]

All three are equally viable candidates from the perspective of conversion sequences (since, of course, they all take the same argument which is an Exact Match for the input). They are all function template specializations, so we can't differentiate on that basis either.

So we're left with function template partial ordering. We synthesize types for each of the overloads and attempt to perform template deduction with our synthesized types against each of the other overloads. The simpler comparison is (1) vs (2) and (1) vs (3). There, we have [temp.deduct.partial]:

If A was transformed from a function parameter pack and P is not a parameter pack, type deduction fails.

Since we transform Types... into UniqA..., and the first parameter is T1, type deduction fails. That makes (2) and (3) both more specialized than (1). So now we compare (2) and (3).

First, can we deduce (2) from (3)?

template <class T1, class... Types> void g(Tuple<T1, Types...> );

g(Tuple<Uniq3, Uniq3Pack&...>());

Sure, no problem T1 == Uniq3 and Types... == Uniq3Pack&.... Next, we try the other direction:

template <class T1, class... Types> void g(Tuple<T1, Types&...> );

g(Tuple<Uniq2, Uniq2Pack...>());

This fails, since Uniq2Pack isn't a pack of reference types and Types&... is. Since deduction only succeeds in one direction, that makes (3) the more specialized overload.

As it's the last one standing, (3) is the best viable candidate. It may seem counterintuitive, since we're not actually calling it with a reference type - but it's still the best choice.