Document #: | P3158R0 |
Date: | 2024-02-15 |
Project: | Programming Language C++ |
Audience: |
Evolution |
Reply-to: |
James Touton <bekenn@gmail.com> |
This paper introduces a new flavor of template template parameter that matches any template template argument, herein referred to as headless template template parameters because they are declared without a template-head. This feature is intended to be used with universal template parameters ([P1985R3], [P2989R0]), but may be adopted separately. This feature obsoletes a form of template argument matching that allows a template template parameter with a template parameter pack to match a template template argument without a template parameter pack (violating the contract implied by the form of the template template parameter). This paper also introduces constrained template template parameter declarations, as constraints are expected to be used frequently in combination with headless template template parameters.
The syntax for the declaration of a universal template parameter is
as yet undecided. [P1985R3] and [P2989R0] offer several possibilities,
each of which has drawbacks associated with introducing a new keyword,
repurposing an old keyword, designating an identifier as a contextual
keyword, or some combination of the above. The examples in those papers
are not consistent in their choice of syntax; for internal consistency,
and to avoid the issues mentioned above, this paper will use
?
to declare a universal
template parameter:
// Example from P2989R0
template <universal template>
constexpr bool is_variable = false;
template <auto a>
constexpr bool is_variable<a> = true;
// Same example, rewritten with ?
template <?>
constexpr bool is_variable = false;
template <auto a>
constexpr bool is_variable<a> = true;
Taken together, [P1985R3] and [P2989R0] present several different models of universal template parameter semantics. For the purposes of this paper, we’ll assume the semantics presented in [P2989R0]; that is:
[P1985R3] discusses a number of additional hypothetical language features that would have interactions with universal template parameters where they to be adopted. As these are not currently part of C++, this paper will not discuss them. The features are:
The primary motivation for this feature is to make universal template parameters useful without backsliding on one of the key principles of [P0522R0], which is that the form of a template template parameter should be indicative of its proper use within its scope. A secondary motivation is to provide an alternative to an existing rule that violates this principle. These two issues are closely related.
[N2555] introduced a change to template template argument matching that allows a template template parameter with a template parameter pack to match a template template argument without a template parameter pack. This was done to grant the ability to write a metafunction that could accept a template and an arbitrary number of template arguments, and then apply the arguments to the template:
template <template <class...> class F, class... Args>
using apply = F<Args...>;
template <class T1> struct A;
template <class T1, class T2> struct B;
template <class... Ts> struct C;
template <class T> struct X { };
<apply<A, int>> xa; // OK
X<apply<B, int, float>> xb; // OK
X<apply<C, int, float, int, short, unsigned>> xc; // OK X
This addressed the immediate need, but it did so in a way that
undermines the contract implied by the template-head of the
template template parameter. In the above example, it would be
reasonable for the author of
apply
to expect that
F
is a template that can accept
any number of type arguments, as is the case for
C
. However,
A
can only accept one argument,
and B
can only accept two
arguments. The contract implied by the declaration of
F
is violated, but the parameter
is allowed to bind anyway. If a metafunction actually does require that
the template accept any number of arguments, then there is no longer any
space in the existing syntax to express that constraint. We’ve taken
syntax that ought to mean one thing and assigned an entirely different
meaning to it.
[N2555] was a reasonable compromise for its time. It addressed a very real need at the cost of a small syntactic carve-out in a feature that nobody had seen before (variadic templates). Today, variadic templates are everywhere, template constraints permit us to make fine-grained decisions based on the content of a template declaration, and with reflection on the horizon, the ability to accurately express intent is more important than ever. If universal template parameters take an approach similar to [N2555], the damage will be much worse.
In the example above, apply
can only operate on templates taking some number of type arguments. If
you wish to apply
non-type or
template arguments to a template that can accept them, you’re out of
luck. There are two reasons for this. The first is that
F
is specified in such a way
that it cannot bind to a template that can accept template or non-type
arguments. The second is that
apply
itself is unable to accept
template or non-type arguments in
Args
. Universal template
parameters are an essential feature well-suited to solving one
of those problems, but [P1985R3] attempts to
solve both of those issues with the same feature by abusing template
template parameter matching in much the same way that [N2555] did:
template <template <?...> typename F, ?... Args>
using apply = F<Args...>; // easy peasy!
// ok, r1 is std::array<int, 3>
using r1 = apply<std::array, int, 3>;
// ok, r2 is std::vector<int, std::pmr::allocator>
using r2 = apply<std::vector, int, std::pmr::allocator>;
As with [N2555], the parameter
pack in the template-head of
F
is misleading. This much
follows from the rules established by [N2555]; it is not a new problem, and we
can’t change the meaning of the parameter pack without a lengthy
deprecation period (assuming there’s any appetite to try; more on this
later). Unlike [N2555], the parameter
kind (the pattern of the parameter pack) is also
misleading.
A naïve reading of the declaration of
F
would interpret it as a
template capable of taking any number of arguments, each of any
kind. However, F
will bind
to any template template argument whatsoever, irrespective of
the number or kinds of template parameters or even any associated
constraints on the argument template.
In the case of apply
, what’s
actually desired is a form of template template parameter that does not
suggest a capability that expresses our lack of knowledge about the
bound template template argument. In each of the prior formulations of
apply
, the
template-head of F
has
conveyed misleading information about the properties of
F
, when what we really want is a
way to say that we don’t know anything about
F
. So let’s get rid of the
template-head entirely:
template <template <?...>
typename F, ?... Args>
using apply = F<Args...>; // easy peasy!
Now, we can actually distinguish between a template that accepts any arguments whatsoever and a template that we know nothing about. We now have the tools we need to fully express intent.
Headless template template parameters are essentially free of any constraints, as they are not constrained by parameter count or kind. It seems likely that one of the first things a programmer will want to do is to add some form of constraint on these parameters. The most obvious constraint is a check for the validity of a set of template arguments:
template <template typename T, ?... Args>
concept specializable = requires { typename T<Args...>; };
To ease the application of constraints, a concept name denoting a template concept may be used to declare a headless template template parameter. This works exactly as it does for type constraints, except that the concept’s prototype parameter must be a template template parameter rather than a type parameter.
template <specializable<int, 5> TT> void f();
// Same as:
// template <template typename TT> requires specializable<TT, int, 5>
// void f();
Following are some examples copied from [P1985R3]. The examples have been updated with diff marks showing the difference in syntax with the application of headless template template parameters. In all cases, the semantics of the example are unchanged. As the difference is entirely in the absence of a template-head, headless template template parameters result in simpler syntax that is also more representative of intent.
// is_specialization_of
template <typename T, template <?...>
typename Type>
constexpr bool is_specialization_of_v = false;
template <?... Params, template <?...>
typename Type>
constexpr bool is_specialization_of_v<Type<Params...>, Type> = true;
template <typename T, template <?...>
typename Type>
concept specialization_of = is_specialization_of_v<T, Type>;
template<typename Type, template <?...>
typename Templ>
constexpr bool is_specialization_of_v = (template_of(^Type) == ^Templ);
template <?> constexpr bool is_typename_v = false;
template <typename T> constexpr bool is_typename_v<T> = true;
template <?> constexpr bool is_value_v = false;
template <auto V> constexpr bool is_value_v<V> = true;
template <?> constexpr bool is_template_v = false;
template <template <?...>
typename A>
constexpr bool is_template_v<A> = true;
// The associated type for each trait:
template <? X> struct is_typename : std::bool_constant<is_typename_v<X>> {};
template <? X> struct is_value : std::bool_constant<is_value_v<X>> {};
template <? X> struct is_template : std::bool_constant<is_template_v<X>> {};
template <?> struct box; // impossible to define body
template <auto X>
struct box<X> { static constexpr decltype(X) result = X; };
template <typename X>
struct box<X> { using result = X; };
template <template <?...>
typename X>
struct box<X> {
template <?... Args>
using result = X<Args...>;
};
template <template <?>
typename Map,
template <?...>
typename Reduce,
?... Args>
using map_reduce = Reduce<Map<Args>::result...>;
template <int... xs> using sum = box<(0 + ... + xs)>;
template <? X> using boxed_is_typename = box<is_typename_v<X>>;
static_assert(2 == map_reduce<boxed_is_typename, sum, int, 1, long, std::vector>::result);
template<typename T>
struct unwrap
{
using result = T;
};
template<typename T, T t>
struct unwrap<std::integral_constant<T, t>>
{
static constexpr T result = t;
};
template <template <?…> typename T, typename... Params>
using apply_unwrap = T<unwrap<Params>::result...>;
<std::array, int, std::integral_constant<std::size_t, 5>> arr; apply_unwrap
template <template <?…> typename F,
? ... Args1>
struct curry {
template <?... Args2>
using func = F<Args1..., Args2...>;
};
template<template<?…> class C, typename... Ps>
auto make_unique(Ps&&... ps) {
return unique_ptr<decltype(C(std::forward<Ps>(ps)...))>(new C(std::forward<Ps>(ps)...));
}
§7.5.5.2 [expr.prim.lambda.closure]/8
[ Note: The function call operator or operator template can be constrained (13.5.3 [temp.constr.decl]) by a
type-constraintconstraint-specifier (13.2 [temp.param]), a requires-clause (13.1 [temp.pre]), or a trailing requires-clause (9.3 [dcl.decl]).[ Example:— end note ]— end example ]template <typename T> concept C1 = /* ... */; template <std::size_t N> concept C2 = /* ... */; template <typename A, typename B> concept C3 = /* ... */; auto f = []<typename T1, C1 T2> requires C2<sizeof(T1) + sizeof(T2)> (T1 a1, T1 b1, T2 a2, auto a3, auto a4) requires C3<decltype(a4), T2> { // T2 is constrained by a
type-constraintconstraint-specifier. // T1 and T2 are constrained by a requires-clause, and // T2 and the type of a4 are constrained by a trailing *requires-clause*. };
§7.5.7.4 [expr.prim.req.compound]
compound-requirement:
{
expression}
noexcept
opt return-type-requirementopt;
return-type-requirement:
->
type-constraintconstraint-specifier
§7.5.7.4 [expr.prim.req.compound]/1
1 A compound-requirement asserts properties of the expression E. Substitution of template arguments (if any) and verification of semantic properties proceed in the following order:
(1.1) Substitution of template arguments (if any) into the expression is performed.
(1.2) If the
noexcept
specifier is present, E shall not be a potentially-throwing expression (14.5 [except.spec]).(1.3) If the return-type-requirement is present, then:
- (1.3.1) Substitution of template arguments (if any) into the return-type-requirement is performed.
- The constraint-specifier shall name a type concept.
- (1.3.2) The immediately-declared constraint (13.2 [temp.param]) of the
type-constraintconstraint-specifier fordecltype((
E))
shall be satisfied.[…]
[…]
§9.2.9.3 [dcl.type.simple]/2
2 The component names of a simple-type-specifier are those of its nested-name-specifier, type-name, simple-template-id, template-name, and/or
type-constraintconstraint-specifier (if it is a placeholder-type-specifier). The component name of a type-name is the first name in it.
§9.2.9.7.1 [dcl.spec.auto.general]
placeholder-type-specifier:
type-constraintoptconstraint-specifieroptauto
type-constraintoptconstraint-specifieroptdecltype
(
auto
)
§9.2.9.7.1 [dcl.spec.auto.general]/2
2 A placeholder-type-specifier of the form
type-constraintoptconstraint-specifieroptauto
can be used as a decl-specifier of the decl-specifier-seq of a parameter-declaration of a function declaration or lambda-expression and, if it is not theauto
type-specifier introducing a trailing-return-type (see below), is a generic parameter type placeholder of the function declaration or lambda-expression.[…]
§9.2.9.7.2 [dcl.type.auto.deduct]/2.1
(2.1) For a non-discarded
return
statement that occurs in a function declared with a return type that contains a placeholder type,T
is the declared return type.[…]
If E has type
void
,T
shall be eithertype-constraintoptconstraint-specifieroptdecltype(auto)
or cvtype-constraintoptconstraint-specifieroptauto
.
§9.2.9.7.2 [dcl.type.auto.deduct]/3
3 If the placeholder-type-specifier is of the form
type-constraintoptconstraint-specifieroptauto
, the deduced typeT
′ replacingT
is determined using the rules for template argument deduction. If the initialization is copy-list-initialization, a declaration ofstd::initializer_list
shall precede (6.5.1 [basic.lookup.general]) the placeholder-type-specifier. ObtainP
fromT
by replacing the occurrences oftype-constraintoptconstraint-specifieroptauto
either with a new invented type template parameterU
or, if the initialization is copy-list-initialization, withstd::initializer_list<U>
. Deduce a value forU
using the rules of template argument deduction from a function call (13.10.3.2 [temp.deduct.call]), whereP
is a function template parameter type and the corresponding argument is E. If the deduction fails, the declaration is ill-formed. Otherwise,T
′ is obtained by substituting the deducedU
intoP
.[…]
§9.2.9.7.2 [dcl.type.auto.deduct]/4
4 If the placeholder-type-specifier is of the form
type-constraintoptconstraint-specifieroptdecltype(auto)
,T
shall be the placeholder alone. The type deduced forT
is determined as described in 9.2.9.6 [dcl.type.decltype], as though E had been the operand of thedecltype
.[…]
§9.2.9.7.2 [dcl.type.auto.deduct]/5
5 For a placeholder-type-specifier with a
type-constraintconstraint-specifier, the constraint-specifier shall name a type concept (13.7.9 [temp.concept]) and the immediately-declared constraint (13.2 [temp.param]) of thetype-constraintconstraint-specifier for the type deduced for the placeholder shall be satisfied.
§9.3.4.6 [dcl.fct]/22
22 An abbreviated function template is a function declaration that has one or more generic parameter type placeholders (9.2.9.7 [dcl.spec.auto]). An abbreviated function template is equivalent to a function template (13.7.7 [temp.fct]) whose template-parameter-list includes one invented type template-parameter for each generic parameter type placeholder of the function declaration, in order of appearance. For a placeholder-type-specifier of the form
auto
, the invented parameter is an unconstrained type-parameter. For a placeholder-type-specifier of the formtype-constraintconstraint-specifierauto
, the invented parameter is a type-parameter with thattype-constraintconstraint-specifier. The invented type template-parameter is a template parameter pack if the corresponding parameter-declaration declares a function parameter pack. If the placeholder containsdecltype(auto)
, the program is ill-formed. The adjusted function parameters of an abbreviated function template are derived from the parameter-declaration-clause by replacing each occurrence of a placeholder with the name of the corresponding invented template-parameter.[…]
§13.2 [temp.param]/1
1 The syntax for template-parameters is:
template-parameter:
type-parameter
parameter-declaration
type-parameter:
type-parameter-key...
opt identifieropt
type-parameter-key identifieropt=
type-id
type-constraintconstraint-specifier...
opt identifieropt
type-constraintconstraint-specifier identifieropt=
type-id
template-head type-parameter-key...
opt identifieropt
template-head type-parameter-key identifieropt=
id-expression
template
type-parameter-key...
opt identifieropt
template
type-parameter-key identifieropt=
id-expression
type-parameter-key:
class
typename
type-constraintconstraint-specifier:
nested-name-specifieropt concept-name
nested-name-specifieropt concept-name<
template-argument-listopt>
The component names of a
type-constraintconstraint-specifier are its concept-name and those of its nested-name-specifier (if any).[…]
§13.2 [temp.param]/3
3 The identifier in a type-parameter is not looked up. A type-parameter whose identifier does not follow an ellipsis defines its identifier to be a typedef-name
(if declared withoutor template-nametemplate
)(if declared within the scope of the template declaration. The identifier is a template-name if it is declared withtemplate
)template
or with a constraint-specifier naming a template concept (13.7.9 [temp.concept]); otherwise, it is a typedef-name.[…]
§13.2 [temp.param]/4
4 A
type-constraintconstraint-specifierQ
that designates a conceptC
can be used to constrain a contextually-determined type, template, or templatetypeparameter packT
with a constraint-expressionE
defined as follows. IfQ
is of the formC<A
1,
⋯, A
n>
, then letE
′ beC<T, A
1,
⋯, A
n>
. Otherwise, letE
′ beC<T>
. IfT
is not a pack, thenE
isE
′,; otherwise,E
is(E
′&& ...)
. This constraint-expressionE
is called the immediately-declared constraint ofQ
forT
. The concept designated by atype-constraintconstraint-specifier shall be a type concept or a template concept (13.7.9 [temp.concept]).
§13.2 [temp.param]/5
5 A type-parameter that starts with a
type-constraintconstraint-specifier introduces the immediately-declared constraint of thetype-constraintconstraint-specifier for the parameter.[…]
§13.2 [temp.param]/11
11 A non-type template parameter declared with a type that contains a placeholder type with a
type-constraintconstraint-specifier introduces the immediately-declared constraint of thetype-constraintconstraint-specifier for the invented type corresponding to the placeholder (9.3.4.6 [dcl.fct]).
§13.2 [temp.param]/17
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 (9.3.4.6 [dcl.fct]), then the template-parameter is a template parameter pack (13.7.4 [temp.variadic]). A template parameter pack that is a parameter-declaration whose type contains one or more unexpanded packs is a pack expansion. Similarly, a template parameter pack that is a type-parameter with a template-parameter-list containing one or more unexpanded packs is a pack expansion. A
typetemplate parameter pack declared with atype-constraintconstraint-specifier that contains an unexpanded parameter pack 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.[…]
§13.3 [temp.names]/7
7 A template-id is valid if the named template is a template template-parameter declared without a template-head or if
- (7.1) there are at most as many arguments as there are parameters or a parameter is a template parameter pack (13.7.4 [temp.variadic]),
- (7.2) there is an argument for each non-deducible non-pack parameter that does not have a default template-argument,
- (7.3) each template-argument matches the corresponding template-parameter (13.4 [temp.arg]),
- (7.4) substitution of each template argument into the following template parameters (if any) succeeds, and
- (7.5) if the template-id is non-dependent, the associated constraints are satisfied as specified in the next paragraph.
A simple-template-id shall be valid unless it names a function template specialization (13.10.3 [temp.deduct]).
[…]
§13.4.4 [temp.arg.template]/3
3 A template-argument
A
matches a template template-parameterP
whenP
is declared without a template-head,A
is the name of a template template-parameter declared without a template-head, or whenP
is at least as specialized asthe template-argumentA
, ignoring constraints onA
ifP
is unconstrained.In this comparison, ifIfP
is unconstrained, the constraints onA
are not considered.P
containsis declared with a template-head containing a template parameter pack (13.7.4 [temp.variadic]), thenA
also matchesP
if each ofA
’s template parameters matches the corresponding template parameter in the template-head ofP
(this behavior is deprecated; see D.# [depr.temp.match]). Two template parameters match if they are of the same kind (type, non-type, template), for non-type template-parameters, their types are equivalent (13.7.7.2 [temp.over.link]), and for template template-parameters, each of their corresponding template-parameters matches, recursively.WhenA template parameter pack in the template-head ofP
’s template-head contains a template parameter pack (13.7.4 [temp.variadic]), the template parameter packP
will match zero or more template parameters or template parameter packs in the template-head ofA
with the same type and form as the template parameter pack inP
(ignoring whether those template parameters are template parameter packs).[ Example:— end example ]template<class T> class A { /* ... */ }; template<class T, class U = T> class B { /* ... */ }; template<class ... Types> class C { /* ... */ }; template<auto n> class D { /* ... */ };
template<template class P> class W { /* ... */ };
template<template<class> classPQ> class X { /* ... */ }; template<template<class ...> classQR> class Y { /* ... */ }; template<template<int> classRS> class Z { /* ... */ };W<A> wa; // OK
W<B> wb; // OK
W<C> wc; // OK
W<D> wd; // OK
<A> xa; // OK X<B> xb; // OK X<C> xc; // OK X<A> ya; // OK Y<B> yb; // OK Y<C> yc; // OK Y<D> zd; // OK Z[ Example:— end example ]template <class T> struct
evalX { }; template <template <class, class...> class TT, class T1, class... Rest>struct eval<TT<T1, Rest...>> { };
using eval1 = TT<T1, Rest...>;
template <template class TT, class T1, class... Rest>
using eval2 = TT<T1, Rest...>;
template <class T1> struct A; template <class T1, class T2> struct B; template <int N> struct C; template <class T1, int N> struct D; template <class T1, class T2, int N = 17> struct E; X<eval1<A<, int>> e1A; // OK, matches partial specialization of evalX<eval1<B<, int, float>> e1B; // OK, matches partial specialization of evalX<eval1<C<, 17>> e1C; // error: C does not match TT inpartial specializationeval1 X<eval1<D<, int, 17>> e1D; // error: D does not match TT inpartial specializationeval1 X<eval1<E<, int, float>> e1E; // error: E does not match TT inpartial specializationeval1X<eval2<A, int>> e2A; //
OKX<eval2<B, int, float>> e2B; //
OKX<eval2<C, 17>> e2C; //
error: non-type argument provided forT1
ineval2
X<eval2<D, int, 17>> e2D; //
error: non-type argument provided forRest
ineval2
X<eval2<E, int, float>> e2E; //
OK[ Example:— end example ]template<typename T> concept C = requires (T t) { t.f(); }; template<typename T> concept D = C<T> && requires (T t) { t.g(); }; template<template<C> class P> struct S { }; template<C> struct X { }; template<D> struct Y { }; template<typename T> struct Z { }; <X> s1; // OK, X and P have equivalent constraints S<Y> s2; // error: P is not at least as specialized as Y S<Z> s3; // OK, P is at least as specialized as Z S
§13.5.3 [temp.constr.decl]/2
2 Constraints can also be associated with a declaration through the use of
type-constraintsconstraint-specifiers in a template-parameter-list or parameter-type-list. Each of these forms introduces additional constraint-expressions that are used to constrain the declaration.
§13.5.3 [temp.constr.decl]/3.3
(3.3) Otherwise, the associated constraints are the normal form of a logical and expression (7.6.14 [expr.log.and]) whose operands are in the following order:
- (3.3.1) the constraint-expression introduced by each
type-constraintconstraint-specifier (13.2 [temp.param]) in the declaration’s template-parameter-list, in order of appearance, and- (3.3.2) the constraint-expression introduced by a requires-clause following a template-parameter-list (13.1 [temp.pre]), and
- (3.3.3) the constraint-expression introduced by each
type-constraintconstraint-specifier in the parameter-type-list of a function declaration, and- (3.3.4) the constraint-expression introduced by a trailing requires-clause (9.3 [dcl.decl]) of a function declaration (9.3.4.6 [dcl.fct]).
§13.7.1 [temp.decls.general]/3
3 For purposes of name lookup and instantiation, default arguments,
type-constraintsconstraint-specifiers, requires-clauses (13.1 [temp.pre]), and noexcept-specifiers of function templates and of member functions of class templates are considered definitions; each default argument,type-constraintconstraint-specifier, requires-clause, or noexcept-specifier is a separate definition which is unrelated to the templated function definition or to any other default arguments,type-constraintsconstraint-specifiers, requires-clauses, or noexcept-specifiers. For the purpose of instantiation, the substatements of a constexpr if statement (8.5.2 [stmt.if]) are considered definitions.
§13.7.7.2 [temp.over.link]/6
6 Two template-heads are equivalent if their template-parameter-lists have the same length, corresponding template-parameters are equivalent and are both declared with
type-constraintsconstraint-specifiers that are equivalent if either template-parameter is declared with atype-constraintconstraint-specifier, and if either template-head has a requires-clause, they both have requires-clauses and the corresponding constraint-expressions are equivalent. Two template-parameters are equivalent under the following conditions:
- (6.1) they declare template parameters of the same kind,
- (6.2) if either declares a template parameter pack, they both do,
- (6.3) if they declare non-type template parameters, they have equivalent types ignoring the use of
type-constraintsconstraint-specifiers for placeholder types, and- (6.4) if they declare template template parameters, either neither has a template-head or they both do and their
template parameterstemplate-heads are equivalent.When determining whether types or
type-constraintsconstraint-specifiers are equivalent, the rules above are used to compare expressions involving template parameters. Two template-heads are functionally equivalent if they accept and are satisfied by (13.5.2 [temp.constr.constr]) the same set of template argument lists.
§13.7.7.3 [temp.func.order]/3
3 To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs (13.7.4 [temp.variadic]) thereof) synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template
.:
(3.1) For a non-type template parameter, the type of the synthesized value is the type of the parameter after substitution of prior template parameters.
[ Note: The type replacing the placeholder in the type of the value synthesized for a non-type template parameter is also a unique synthesized type. — end note ]
(3.2) For a template template parameter declared with a template-head, the template-head of the synthesized class template is the result of substitution into the template-head of the template template parameter. For a template template parameter declared without a template-head, the synthesized class template does not match (13.4.4 [temp.arg.template]) any template template-parameter declared with a template-head.
[…]
§13.7.9 [temp.concept]/2
2 A concept-definition declares a concept. Its identifier becomes a concept-name referring to that concept within its scope. The optional attribute-specifier-seq appertains to the concept.
[ Example:— end example ]template<typename T> concept C = requires(T x) { { x == x } -> std::convertible_to<bool>; }; template<typename T> requires C<T> // C constrains f1(T) in constraint-expression (T x) { return x; } T f1 template<C T> // C, as a
type-constraintconstraint-specifier, constrains f2(T) (T x) { return x; } T f2
§13.7.9 [temp.concept]/7
7 The first declared template parameter of a concept definition is its prototype parameter. A type concept is a concept whose prototype parameter is a type template-parameter. A template concept is a concept whose prototype parameter is a template template-parameter.
§13.9.2 [temp.inst]/2
2 […]
[ Note: Within a template declaration, a local class (11.6 [class.local]) or enumeration and the members of a local class are never considered to be entities that can be separately instantiated (this includes their default arguments, noexcept-specifiers, and non-static data member initializers, if any, but not their
type-constraintsconstraint-specifiers or requires-clauses). As a result, the dependent names are looked up, the semantic constraints are checked, and any templates used are instantiated as part of the instantiation of the entity within which the local class or enumeration is declared. — end note ]
§13.9.2 [temp.inst]/17
17 The
type-constraintsconstraint-specifiers and requires-clause of a template specialization or member function are not instantiated along with the specialization or function itself, even for a member function of a local class; substitution into the atomic constraints formed from them is instead performed as specified in 13.5.3 [temp.constr.decl] and 13.5.2.3 [temp.constr.atomic] when determining whether the constraints are satisfied or as specified in 13.5.3 [temp.constr.decl] when comparing declarations.
§D [depr]
D.# Matching template template-parameters with parameter packs [depr.temp.match]
1 A template parameter pack in the template-head of a template template-parameter is permitted to match zero or more template parameters in the template-head of a template template-argument (13.4.4 [temp.arg.template]). This behavior is deprecated.
[ Example:— end example ]template <template <class... Ts> class TT> class X { }; template <class A, class B> class Y; <Y> x; // deprecated, TT matches A and B X template <template <class A, class B> class TT> class P { }; template <class... Ts> class Q; <Q> p; // OK P