Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do variadic type template parameters and non-type template parameters of the template template parameter of a nested class constrain each other?

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.

like image 809
cigien Avatar asked Nov 26 '20 03:11

cigien


People also ask

What is Variadic template in C++?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.

What is non-type template parameters?

A template non-type parameter is a template parameter where the type of the parameter is predefined and is substituted for a constexpr value passed in as an argument. A non-type parameter can be any of the following types: An integral type. An enumeration type. A pointer or reference to a class object.

Can we use non-type parameters as argument templates?

A non-type template argument provided within a template argument list is an expression whose value can be determined at compile time. Such arguments must be constant expressions, addresses of functions or objects with external linkage, or addresses of static class members.

What can be passed by non-type template parameters during compile time?

What can be passed by non-type template parameters during compile time? Explanation: Non-type template parameters provide the ability to pass a constant expression at compile time. The constant expression may also be an address of a function, object or static class member.

What is a template non-type parameter?

A template non-type parameter is a template parameter where the type of the parameter is predefined and is substituted for a constexpr value passed in as an argument. A non-type parameter can be any of the following types:

What is variadic template in C++11?

C++11 lets us define variadic templates, taking any amount of parameters, of any type, instead of just a specific number of parameters. Then, when we use the template, we can not only specify the types, we can specify an arbitrary amount of different types. For instance, template <class... T_values>

Is it okay to use Variadic templates?

It would be understandable for coding guidelines to discourage the use of variadic templates except in special cases, until people are more familiar with them. Of course, you might need to call methods with just some of the parameters from the parameter pack, or some combination of parameter packs.

What is a template parameter in C++?

An identifier that names a non-type template parameter of class type T denotes a static storage duration object of type const T, called a template parameter object, whose value is that of the corresponding template argument after it has been converted to the type of the template parameter.


2 Answers

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)

like image 177
AndyG Avatar answered Nov 13 '22 21:11

AndyG


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.

like image 44
xmh0511 Avatar answered Nov 13 '22 19:11

xmh0511