6

I'd like to enforce a CRTP template parameter to be an actual derived type. Unfortunately it seems to be impossible to express that as a constraint.

The problem is that the constrained type is undeclared in the requires clause - and a forward declaration would need the same requires clause / constraint. Another problem is that the derived class is actually incomplete at the instantion point.

It is a recursive constraint...

Is there any way to write such a constraint?

#include <type_traits>

template<typename TDerived>
    requires std::is_base_of_v<Base<TDerived>, TDerived> //<-- this fails
class Base
{
};

class Sub : public Base<Sub>
{    
};

/* this should be not allowed
class Sub : public Base<int>
{    
};
*/

int main(int argc, char* argv[])
{
    Sub s;
    return 0;
}
2
  • 1
    Even if it worked, I guess it would not prevent anyone to write class OtherSub : public Base<Sub> {}; // Oops it depends on Sub instead of OtherSub which is probably something you didn't intend to allow. Commented Sep 15 at 14:11
  • At this stage Base<TDerived> is incomplete, so it can't be base of anything. Your concept definition attempts to create infinitive recursion. This concepts depends on it self. Commented Sep 15 at 14:50

2 Answers 2

6

In this case, you can enforce it by adding a static_assert in the relevant constructor of Base (here a default constructor):

#include <type_traits>

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

Live demo - Godbolt

Note:
As @Fareanor commented, this will not protect against a case like class OtherSub : public Base<Sub> {} which you might want to prevent.
But anyway in this respect the requires clause that you asked about would behave the same if it worked.

Sign up to request clarification or add additional context in comments.

7 Comments

Good idea - sadly that it seems to impossible via concept / requires clause
I haven't succeeded to use a reuiqres and so suggested this workaround. Maybe some other user here will come up with a "trick" with requires.
I think this is not enough. It doesn't prevent anyone to declare a class OtherSub : public Base<Sub> {}; // Oops it depends on Sub instead of OtherSub which is probably not what we intend to do with CRTP.
You are right. But if the requires you suggested would have worked it would accept OtherSub as well, isn't it ?
I didn't make any suggestion, I'm not the OP :) But you are right, the proposed requires clause would have the same flaw.
@Fareanor - oops, a silly mistake. Anyway at least the workaround is similar in that sense to the OP's idea.
Yes, that is why I moved my comment into the question in order to know the OP position about it :)
3

You could indirectly prohibit instantiating objects if they are of the form

class S2 : public Base<S1> {};

by adding a private destructor to Base and making TDerived a friend of Base (this would not allow any real "private" members inside Base, should you really need that).

template<typename TDerived>
class Base
{
private:
    ~Base() = default;
    friend TDerived;
public:
    // rest of Base definition
};

class Sub1 : public Base<Sub1> { 
    friend class Sub3; // "Accidentially" friending Sub3 seems not to change anything
};

// These definitions are fine, but trying to create either object will fail
// MSVC at least already gives warnings
class Sub2 : public Base<int> { };
class Sub3 : public Base<Sub1> { public: void foo() {} };

int main()
{
    [[maybe_unused]]
    Sub1 s1; // Works fine

    //Sub2 s2; // error: call to implicitly-deleted default constructor of 'Sub2'
    //Sub3 s3; // error: call to implicitly-deleted default constructor of 'Sub3'
    
    // Creating a pointer actually works on MSVC (only deleting fails to compile)
    //Sub3 * s3p = new Sub3;
    //delete s3p;

    // Bonus: Instantiating a "raw" Base class is prohibited as well:
    //Base<Sub1> base;
    [[maybe_unused]]
    Base<Sub1>& s1Ref = s1; // this works of course
}

Godbolt demo: https://godbolt.org/z/qKv9KW8n3

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.