Search code examples
c++c++17language-lawyervariadic-templatestemplate-argument-deduction

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


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?


Solution

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