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