Constrained CRTP Premature Rejection

Viewed 89

I'm trying to implement a derived class inheriting from a base template, with the derived class as its template parameter (the example below hopefully clears things up):

template <class T>
struct S
{
    T f() {return T();}
};

struct D : public S<D>
{
};

This compiles and works well on gcc, clang, and msvc as well. Now, I want to "make sure" that the template parameter inherits from the base class:

#include <concepts>

template <class T>
concept C
= requires ( T t )
{
    { t.f() };
};

template <C T>
struct S
{
    T f() {return T();}
};

struct D : public S<D>
{
};

However, this gets rejected by every compiler, with clang providing the most insight:

error: constraints not satisfied for class template 'S' [with T = D]
struct D : public S<D>
                  ^~~~
note: because 'D' does not satisfy 'C'
template <C T>
          ^
note: because 't.f()' would be invalid: member access into incomplete type 'D'
    { t.f() };

I understand where the compiler is coming from: D is not fully defined yet when the constraint has to be checked, so it fails in lieu of the necessary information. That said, I'm kinda disappointed that no attempt is made to complete the definition of the derived class before evaluating a yet uncheckable constraint.

Is this behaviour intended? Is there another way to check the inheritance that actually works?

By the way, gcc gives a rather useless error message in this case.

1 Answers

You can check the requirement in the default constructor of the base class

#include <type_traits>

template<class Derived>
class Base
{
public:
    Base()
    {
        static_assert(std::is_base_of_v<Base<Derived>, Derived>);
    }
};

class Derived : public Base<Derived>
{ };

This must also be checked in any other user defined non-copy and non-move constructors of base. This is valid as Derived is fully defined when the constructor is instantiated.

Related