I am trying to reproduce the result from video C++ Weekly - Ep 48 - C++17's Variadic using
, but failed. The problem can be simplified to the following code piece.
Suppose I have such generic structure:
template <class... T>
struct Container {
template <class... U>
Container(U... us) {}
};
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. To resolve this, we should provide a deduction guide, e.g.
template <class... U>
Container(U...) -> Container<double, int, bool>
According to the video, the compiler now should know d
has type Container<double, int, bool>
.
However, the code does not work as expected. When printing typeid(d).name()
, the output will always be 9ContainerIJEE
, which is translated to Container<>
, no matter how I change the return type in the deduction guide, indicating that such guide does not guide the compiler at all.
I am using gcc-7-snapshot-20170402
, the compiler in the video is gcc-7-snapshot-20170130
.
Could anyone tell me what is wrong here?
By the way, if I explicitly write
Container<bool, int> d = Container(1,2,3);
Container<char, char, char> d = Container(1,2,3);
...
the code will always compile, and provide outputs like 9containerIJbiEE
and 9containerIJcccEE
.
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 functionF2
if [...]
F1
andF2
are function template specializations, and the function template forF1
is more specialized than the template forF2
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]) andF2
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<>
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With