Search code examples
c++templatesstandardsundefined-behaviorc++23

Concept vs. Typename Parameter in Template Template


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!


Solution

  • 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