I was curious if this is defined behavior in C++23 to have a template
-template
that takes a typename
, and be able to insert a template
which accepts a concept
(which accepts
a typename
)?
Here is an example:
#include <iostream>
#include <type_traits>
template<typename T>
concept MyConcept = std::is_integral_v<T> || std::is_floating_point_v<T>;
template<template<typename> typename TT>
struct A {
template<typename T>
using Type = TT<T>;
};
template<MyConcept T>
struct B {
T bb;
};
int main(int argc, char** args)
{
auto a = A<B>{}; // Ok? Compiles with gcc 13.2.1 and clang-16.0.6
auto b = decltype(a)::Type<int>{};
return 0;
}
The converse also seems to compile which makes me more suspicious
template<template<MyConcept> typename TT>
struct A {
template<typename T>
using Type = TT<T>;
};
template<typename T>
struct B {
T bb;
};
int main(int argc, char** args)
{
auto a = A<B>{}; // Ok? Compiles with gcc 13.2.1 and clang-16.0.6
auto b = decltype(a)::Type<int>{};
return 0;
}
Here, under a certain reading, we should accept a template
into the template
-template
iff that template accepts a MyConcept
, but B
's template
is unconstrained?
Are either of these examples undefined behavior, or are they behaving as the standard defines?
Thank you!
According to the publicly available working draft of the C++23 standard it seems like this is defined behavior and is correct. Example 4 under § 13.4.4 paragraph 3 seems to show exactly this, from the standard:
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 { };
S<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
Also this from example 2 looked relevant:
template<auto n> class D { /* ... */ };
//...
template<template<int> class R> class Z { /* ... */ };
//...
Z<D> zd; // OK