Home > Net >  Is it possible to initialize the template inner class in the C 20 requires clause?
Is it possible to initialize the template inner class in the C 20 requires clause?

Time:12-20

Consider the following class A that defines a template inner class B:

struct A {
  template<class = int>
  struct B { };
};

We can use the following expression to initialize inner B, where typename is optional: (Godbolt)

int main() {
  A::template B<>();
  typename A::template B<>();
}

I want to use concept to detect whether a type has a template inner class B:

template<class T>
concept C = requires {
  typename T::template B<>();
};

static_assert(C<A>);

But only Clang accepted the above code, GCC and MSVC rejected it due to syntax error (Godbolt):

<source>:8:27: error: expected ';' before '(' token
    8 |   typename T::template B<>();
      |                           ^
      |                           ;

And if I remove the typename in the require clause:

template<class T>
concept C = requires {
  T::template B<>();
};

MSVC accepted it, but Clang and GCC will produce static assertion failed since they think the expression is not well-formed (Godbolt):

<source>:11:15: note: because 'A' does not satisfy 'C'
static_assert(C<A>);
              ^
<source>:8:15: note: because 'T::template B<>()' would be invalid: 'A::B' instantiated to a class template, not a function template
  T::template B<>();
              ^

Which compiler should I trust?

CodePudding user response:

My understanding of concepts is, that if you want to check such cases, you have to use the compound statement.

struct A 
{
    template<class = int>
        struct B { };
};

template<class T>
concept C = requires 
{
    { typename T::template B<>() };
};

static_assert(C<A>);

I believe you can't use it within a simple requirement clause, because T::template B<> is a dependent name and needs the keyword typename in front because the concept itself is a template and you are sitting in a unevaluated context.

If you are using the keyword typename in front, it is not longer a simple requirement but becomes a type requirement. But this one checks for the existence of the type and is not intended to build a expression to instantiate the type.

As this you end up in a compound expression where you are able to get the type with keyword typename in the dependent template and do the default initialization of that type.

As alternative, shown as C2, you can pick your type upfront in the concept template parameter list which enables you to use the found type without the keyword typename in the simple requires clause.

I also added test cases to see the concept failing by "killing" the default constructor of your inner type B.

struct A 
{
    template<class = int>
        struct B { };
};

struct A2  
{
    template<class = int>
        struct B { B()=delete; };
};


template<class T>
concept C = requires 
{
    { typename T::template B<>() };
};


template<class T, typename Inner = typename T::template B<> >
concept C2 = requires 
{
    Inner();
};


static_assert(C<A>);
static_assert(C<A2>); // fails as expected

static_assert(C2<A>);
static_assert(C2<A2>); // fails as expected

The above code works for all your three given compilers: explore

  • Related