15
votes

Consider the following structure: S is a class template with a variadic pack of template type parameters ...Ts. The class contains a nested class template N, which has a single template template parameter C. C itself is templated with a variadic pack of non-type template parameters that have exactly the types Ts....

template <typename ...Ts>
struct S 
{
    template <template <Ts...> typename C>
    struct N 
    {
        C<42> c;
    };
};

GCC rejects the declaration of c with:

error: expansion pattern '<anonymous>' contains no parameter packs
        C<42> c;
            ^

I don't know what this diagnostic means, and I'm guessing this is a GCC bug.


Clang accepts this structure, and also accepts a reasonable instantiation such as:

template<int> struct W {};
S<int>::N<W> w;             // ok, makes sense

Of course, the declaration C<42> c; itself places constraints on the template used as an argument for the parameter C, which will be required even if the Ts... in C were simply auto.... e.g. the first argument of Ts... must be a type that is implicitly convertible from an int. Also, the remaining parameters in Ts..., if any, must have defaults.

template<bool(*)()> struct X1 {};
S<int>::N<X1> x1;                  // error: value of type 'int' is not implicitly convertible to 'bool (*)()'
        
template<int, char> struct X2 {};
S<int>::N<X2> x2;                  // error: too few template arguments for class template 'X2'

Interestingly, there do appear to be constraints imposed by the relationship between ...Ts and Ts.... e.g. all the specified arguments for S must now be valid non-type template parameter types:

template<int> struct Y {};
S<int, void>::N<Y> y;       // error: a non-type template parameter cannot have type 'void' 

On the other hand, Clang also accepts instantiations with seemingly incompatible arguments for S, and non-type template parameters for C:

template<int> struct Z {};
S<bool(*)(), bool>::N<Z> z;  // ok ?? But why? both number and type of 'Ts' is different 

Here's a demo.

So what are the rules for how ...Ts and Ts... constrain each other in this structure?


I came across this issue while trying to understand this question where it's indicated that MSVC doesn't accept the code as well.

2
An interesting question is if the default arguments should apply to Z when named as C. If yes, then checking N is harder; if not, then gcc is right.Yakk - Adam Nevraumont
@Yakk-AdamNevraumont I'm not sure I understand why the default arguments are important. If the declaration in N is C<1> n; then b is accepted as well, and X has no default arguments.cigien
Even without expected default, gcc complains Demo.Jarod42
@cigien if default arguments are not "passed through" then your template is only valid for an empty pack, which is illegal last I checked (ill-formed, ndr). C<1> case does not have that problem.Yakk - Adam Nevraumont
@cigien Also, parameter packs containing only the number 42 are also ill-formed, ndr -- that is known as the towel rule. (this is a joke, as conveying humour on the internet is difficult without being explicit)Yakk - Adam Nevraumont

2 Answers

4
votes

For your primary question (nested class with non-type template arguments dependent on template arguments in outer class) this is a bug in GCC, #86883 (See comment #3)

2
votes

I think Clang is not right.
temp.param#15

A template parameter pack that is a parameter-declaration whose type contains one or more unexpanded parameter packs is a pack expansion.

For the template template parameter of nested template class N, that is template <Ts...> typename C, where Ts... is a pack instantiation, which has the following rule:

Each Ei is generated by instantiating the pattern and replacing each pack expansion parameter with its ith element. Such an element, in the context of the instantiation, is interpreted as follows:

  • if the pack is a template parameter pack, the element is a template parameter of the corresponding kind (type or non-type) designating the type or value from the template argument;

So, for this example S<bool(*)(), bool>::N<Z> z; , instantiate Ts... would give a list bool(*)(), bool. Hence, the question can be simplified to:

template<template<bool(*)(), bool> class C>
struct Nested{};
template<int> struct Z {};
int main(){
  Nested<Z> z; // is well-formed?
}

Per temp.arg.template#3

A template-argument matches a template template-parameter P when P is at least as specialized as the template-argument A.

Does the argument Z match the parameter C? According to this rule temp.arg.template#4

A template template-parameter P is at least as specialized as a template template-argument A if, given the following rewrite to two function templates, the function template corresponding to P is at least as specialized as the function template corresponding to A according to the partial ordering rules for function templates. Given an invented class template X with the template parameter list of A (including default arguments):

  • Each of the two function templates has the same template parameters, respectively, as P or A.
  • Each function template has a single function parameter whose type is a specialization of X with template arguments corresponding to the template parameters from the respective function template where, for each template parameter PP in the template parameter list of the function template, a corresponding template argument AA is formed. If PP declares a parameter pack, then AA is the pack expansion PP... ([temp.variadic]); otherwise, AA is the id-expression PP.

If the rewrite produces an invalid type, then P is not at least as specialized as A.

That means, we have an invented template class X has the form:

template<int>
struct InventedX{};

Then, the rewritten function template for A has the form:

template<int N>
auto iventend_function(X<N>);

while the rewritten function template for P has the form:

template<bool(*A)(), bool B>
auto invented_function(X<A,B>) // invalid type for X<A,B>

So, P is not at least as specialized as A. Hence A cannot match with P. So, S<bool(*)(), bool>::N<Z> z; shall be ill-formed.