All the examples are from here and here.
Specifically,
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
With #2
uncommented, g()'s are resolved as described in the comments. What surprises me is that if I comment out the #2
line, the g()'s calls are resolved as follows:
g(Tuple<>()); // calls #1
g(Tuple<int, float>()); // calls **#1 ???? why not #3????**
g(Tuple<int, float&>()); // calls #3
g(Tuple<int>()); // calls #3
From the following examples and explanations below I can't see why g(Tuple<int, float>());
can't resolved to #3
. It is the direction application of the following two rules:
If a parameter pack appears as the last P, then the type P is matched against the type A of each remaining argument of the call. Each match deduces the template arguments for the next position in the pack expansion.
template<class ... Types> void f(Types& ...);
void h(int x, float& y) {
const int z = x;
f(x, y, z); // P=Types&..., A1=x: deduces the first member of Types... to int
// P=Types&..., A2=y: deduces the second member of Types... to float
// P=Types&..., A3=z: deduces the third member of Types... to const int
// calls f<int, float, const int>
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.
There's a misconception here between your two examples. In the 2nd example with f
, you are deducing reference arguments to a function. In the 1st example with g
, you are deducing reference template parameters to an argument to a function. The latter must match exactly, but the former is deduced against the referred types. They are not the same.
In your fist example,
g(Tuple<int, float>());
cannot call g(Tuple<T1, Types&...>)
. The template deduction process is about picking a deduced argument type that is identical to the called argument type. There are some exceptions (for referenced cv-qualifications, pointers, derived classes, arrays, functions), but none of those apply here. We simply need to pick T1
and Types...
such that Tuple<T1, Types&...>
is the same type as Tuple<int, float>
. This is impossible as there is no such pack Types...
for which Types&...
is {float}
, since float
is not a reference!
So once you comment out (2), there's only one viable candidate: (1).
On the other hand,
template<class ... Types> void f(Types& ...);
void h(int x, float& y) {
const int z = x;
f(x, y, z);
}
Here, Types&...
is actually the type of the parameter itself (rather than a template argument of it), so (temp.deduct.call):
If P is a reference type, the type referred to by P is used for type deduction.
We deduce Types...
to match the arguments. This succeeds because all the arguments are lvalues, and we simply pick {int, float, const int}
.