Is it legal to use an unexpanded parameter pack as the type of a template template parameter's non-type template parameter?

Viewed 171

gcc and clang disagree about whether the following code should compile:

template <typename... Args>
struct tuple {};

template <typename>
struct Test;

template <
    typename... Types,
    template <Types> typename... Outer, // XXX
    Types... Inner
>
struct Test<tuple<Outer<Inner>...>> {};

template <long T> struct O1 {};
template <unsigned T> struct O2 {};

Test<tuple<O1<1>, O2<2>>> test;

clang accepts the code, deducing Types = {long, unsigned}, Outer = {O1, O2}, Inner={1L, 2U}. Structurally, this seems correct.

gcc rejects the code with a deduction failure. Interestingly, it does accept if O2 is changed to take a long non-type template parameter, which seems inconsistent to me. It suggests that Types can be expanded if Types = {long, long} but not if Types = {long, unsigned}.

However, it's not clear to me from the standard which compiler is correct. The core question is: on the line denoted XXX, is it valid to have a parameter pack as the type of the template template parameter's non-type template parameter? Should it expand the way that clang claims it does?

1 Answers

It is not valid because:

a type parameter pack cannot be expanded in its own parameter clause.

As from [temp.param]/17:

If a template-parameter is a type-parameter with an ellipsis prior to its optional identifier or is a parameter-declaration that declares a pack ([dcl.fct]), then the template-parameter is a template parameter pack. A template parameter pack that is a parameter-declaration whose type contains one or more unexpanded packs is a pack expansion. ... A template parameter pack that is a pack expansion shall not expand a template parameter pack declared in the same template-parameter-list.

So consider the following invalid example:

template<typename... Ts, Ts... vals> struct mytuple {}; //invalid

The above example is invalid because the template type parameter pack Ts cannot be expanded in its own parameter list.

For the same reason, your code example is invalid.

Related