g++, clang++, and MSVC (pre 2018) all accept the following C++17 code, resulting in the output "unsigned int" then "int":
#include <iostream>
void print_type(int) { std::cout << "int\n"; }
void print_type(unsigned int) { std::cout << "unsigned int\n"; }
template <typename ...T>
void print_types(T ...args)
{
(print_type(args),...);
}
int main()
{
print_types<unsigned int>(1, 1);
}
I agree that this ought to work this way, but I'm having trouble finding a description of why and exactly how in the Standard.
First there's [temp.deduct]/2 describing the processing of explicit template arguments before doing the rest of template argument deduction:
[T]he following steps are performed when evaluating an explicitly specified template argument list with respect to a given function template:
... There must not be more arguments than there are parameters unless at least one parameter is a template parameter pack, and there shall be an argument for each non-pack parameter....
The specified template argument values are substituted for the corresponding template parameters as specified below.
In the example, unsigned int
is certainly a "specified template argument value". But if its "corresponding template parameter" T
gets substituted now, it's difficult to see how it could become a longer list of types later.
For the template argument deduction process, there's [temp.deduct.call]/1:
For a function parameter pack that occurs at the end of the parameter-declaration-list, deduction is performed for each remaining argument of the call, taking the type
P
of the declarator-id of the function parameter pack as the corresponding function template parameter type. Each deduction deduces template arguments for subsequent positions in the template parameter packs expanded by the function parameter pack.
I take "remaining argument of the call" here to mean arguments after the ones that correspond to function parameters that are not the final function parameter pack. But that would mean that in my example, the first function argument 1
is used to deduce T=int
. Does this deduction actually happen, but then get discarded/overridden by the T=unsigned int
from the explicit template argument?
Or maybe "remaining argument of the call" is supposed to mean function arguments after the ones that do not correspond to the final function parameter pack AND after any that correspond to parameter types generated from explicit template arguments; and "subsequent positions in the template parameter packs expanded by the function parameter pack" is supposed to mean sequential positions after any filled by explicit template arguments, but this would be far from clear. And if so, it's also confusing that there is a list of parameter types associated with the function parameter pack but it's still a function parameter pack.
[Another possible implementation giving the expected behavior would be: when one or more explicit template arguments A_1
, ..., A_k
correspond to a template parameter pack P
, invent another template parameter pack More_P
of the same kind, and substitute each expansion of P
with the template argument list {A_1
, ..., A_k
, More_P...
}. Then More_P
can be deduced like any other template parameter pack. If More_P
is never deduced, substitute an empty list for all its expansions before evaluating semantics as for all other deduced substitutions. But there's even less justification for this interpretation in the Standard.]
Have I missed something in the Standard that better describes how explicit template arguments and deduced template arguments can work together to form a single list for one template parameter pack?
unsigned int
is specified explicitly andint
is implicit and gets deduced from the corresponding argument. – bipll