Now I can initialize a Container with any arguments, like
auto d = Container(1,2,3);
However, the compiler will never know what type the d
is.
That's not entirely accurate. The type of d
is Container<>
here. Let's move away from template deduction for constructors and go to just simple function templates:
template <class... Ts, class... Us>
void foo(Us... );
foo(1, 2, 3);
That function call is perfectly ok - we deduce Us
to be {int,int,int}
and we deduce Ts
to be {}
. That is because of [temp.arg.explicit]/3:
A trailing template parameter pack not otherwise deduced will be deduced to an empty sequence of template arguments. If all of the template arguments can be deduced, they may all be omitted; in this case, the empty template argument list <>
itself may also be omitted.
Now, what's a trailing template parameter pack? It's unspecified. But here Ts...
can be deduced as empty, so it is. Note that this has some odd implications, like:
template <class... Ts> void g(std::tuple<Ts...> );
g({}); // ok, calls g<>?
See also this brief discussion.
Getting back to the original question. The declaration
Container d(1, 2, 3);
without any deduction guides is well-formed, because we can succeed in doing template deduction and deduce Ts...
as being empty. That is, it's exactly equivalent to:
Container<> d(1, 2, 3);
So what happens when we add a deduction guide? Now, we're effectively performing overload resolution between:
template <class... Ts, class... Us>
Container<Ts...> f(Us... ); // the constructor template
template <class... Us>
Container<double, int, bool> f(Us... ); // the deduction guide
f(1, 2, 3); // what does this return?
The tiebreakers in determining the best viable candidate are in [over.match.best], where the relevant two are:
Given these definitions, a viable function F1
is defined to be a better function than another viable function F2
if [...]
F1
and F2
are function template specializations, and the function template for F1
is more specialized than the template for F2
according to the partial ordering rules described in [temp.func.order], or, if not that,
F1
is generated from a deduction-guide ([over.match.class.deduct]) and F2
is not, or, if not that, [...]
Function template partial ordering is really quite complicated and the compilers don't quite agree on what to do in all cases. But in this case, I'd say this is a gcc bug (I submitted 80871). According to [temp.deduct.partial]:
The types used to determine the ordering depend on the context in which the partial ordering is done:
- In the context of a function call, the types used are those function parameter types for which the function call has arguments.
That is, the Ts...
in the first function template (the one synthesized from the constructor) aren't used for partial ordering. This makes both function templates identical, and so neither is more specialized than the other. We should then fall into the next bullet, which tells us to prefer the deduction guide over the constructor and end up with Container<double, int, bool>
. gcc, however, believes for some reason that the first function template is more specialized and hence picks it before getting to the deduction-guide tiebreaker, which is why it ends up with Container<>
.