Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deduction guide for variadic template constructor fails

Tags:

c++

gcc

c++17

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?

Update:

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.

like image 467
HanXu Avatar asked Apr 09 '17 07:04

HanXu


1 Answers

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<>.

like image 157
Barry Avatar answered Oct 23 '22 05:10

Barry