C++20 allows the program to specify concept for template template argument. For example,
#include <concepts>
template <typename T> concept Char = std::same_as<T, char>;
template <typename> struct S {};
template <template <Char U> typename T, typename U> T<U> foo() { return {}; }
int main() { foo<S, int>(); }
the first template argument of the function foo
is expected to be a single argument template.
The concept Char
is defined to be true only the type char
, so an attempt to satisfy it for int
shall fail. Still above program is accepted by all compilers: https://gcc.godbolt.org/z/PaeETh6GP
Could you please explain, why the concept in template template argument can be specified, but it will be still ignored?
8. Why we use :: template-template parameter? Explanation: It is used to adapt a policy into binary ones.
A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)
For example, given a specialization Stack<int>, “int” is a template argument. Instantiation: This is when the compiler generates a regular class, method, or function by substituting each of the template's parameters with a concrete type.
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.
A template (actual) argument matches a template (formal) parameter if the latter is at least as specialised as the former.
template <Char> typename T
is more specialised than template <typename> struct S
. Roughly speaking, template <Char>
accepts a subset of what template <typename>
accepts (the exact definition of what "at least as specialised" actually means is rather involved, but this is a zeroth approximation).
This means that the actual argument can be used in all contexts where the formal parameter can be used. That is, for any type K
for which T<K>
is valid, S<K>
is also valid (because S<K>
is valid for any K
).
So it is OK to substitute S
for T
.
If you do it the other way around:
template<typename T> concept Any = true;
template<typename T> concept Char = Any<T> && std::same_as<T, char>;
template<template<Any> class T> void foo();
template<Char> struct S { };
int main()
{
foo<S>();
}
then this is ill-formed, because (roughly) you can say T<int>
but not S<int>
. So S
is not a valid substitute for T
.
Notes:
Any
? What's wrong with simply saying template <template <typename> typename>
? Well, that's because of a special rule: if the parameter is not constrained at all, constraints of the argument are ignored, everything goes.Any<T> && std::same_as<T, char>;
? To illustrate a point. The actual rules do not evaluate the boolean values of constraints, but compare constraints as formulae where atomic constraints serve as variables, see here. So the formal reason is that S
has a conjunction of a strictly larger (inclusion-wise) set of atomic constraints than T
. If S
had the same or a strictly smaller set, it would be well-formed. If two sets are not ordered by inclusion, then neither template is more specialised, and there is no match.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With