I stumbled upon this:
#include <type_traits>
#include <concepts>
template<class T>
concept IsFoo = requires(T a)
{
{a.a} -> std::same_as<int>;
};
#if 1
// Will not compile, because Foo currently has incomplete type
template<IsFoo AFoo>
struct AcceptsFoo
{};
#else
template<class AFoo>
struct AcceptsFoo
{};
#endif
struct Foo
{
int a;
int b;
AcceptsFoo<Foo> obj;
};
https://gcc.godbolt.org/z/j43s4z
Other variant (crtp) https://gcc.godbolt.org/z/GoWfhq
Foo is incomplete because it has to instantiate AcceptsFoo
, but to do so, Foo
has to be complete, otherwise it cannot check IsFoo
. Is this a bug in GCC, or does the standard say so? The latter would be sad, because this prevents concepts for being used together with some well-known patterns such as CRTP.
I noticed that clang does gives a similar error: https://gcc.godbolt.org/z/d5bEez
You can check a concept against an incomplete type - but if that concept check requires actually doing anything with the type that requires it to complete, that's going to fail the concept check.
And even there, you have to be careful since implementations are allowed to (and will) cache concept checks to compile faster - so if you try C<T>
while T
is incomplete and try again when T
becomes complete, and those should give different answers, you're asking for trouble.
Foo
isn't complete at the point you're checking it, so naturally it doesn't have a member named a
. This really cannot possibly work.
because this prevents concepts for being used together with some well-known patterns such as CRTP.
Being used together in this way, yes. In the same way that with CRTP you also can't access anything directly off of the template parameter passed into the base class, you always have to be careful to delay any instantiation of that type until it is complete.
This is ultimately the same reason:
template <typename Derived>
struct B {
typename Derived::type x;
};
struct D : B<D> {
using type = int;
};
does not work.