Whatever programming style you use, you can alwyas do something bad: even if you follow the best of the bestest guideline practice. That's something physical behind it (and relate to the impossibility to reduce the global entrophy)
That said, don't confuse "classic OOP" (a methodology) with C++ (a language), OOP inheritache (a relation) with C++ inheritance (an aggregation mechanism) and OOP polymorphism (a model) with C++ runtime and static polymorphism (a dispatching mechanism).
Although names sometime matches, the C++-things don't have to necessarily sevicing OOP-things.
Public inheritance from a base with some non-virtual methods is normal. and destructor is not special: just dont call delete on the CRTP base.
Unlike with classic OOP, a CRTP-base has different type for each of the deriveds, so having a "pointer to a base" is clueless since there is no "pointer to a common type". And hence the risk to call "delete pbase" is very limited.
The "protected-dtor paradigm" is valid only if you are programming OOP-inheritance using C++ inheritance for object managed (and deleted) though pointer-based polymorphism. If you are following other paradigms, those rules should not be treated in a literal way.
In your case, the proteced-dtor just deny you to create a Base<Derived>
on the stack and to call delete on a Base*. Something you will never do, since Base with no "Dervied" has no sense to exist, and having a Base<Derived>*
makes no sense since you can have just a Derived*
, hence having both public ctor and dtor makes no particular mess.
But you can even do the opposite choice to have both ctor and dtor protected, since you will never construct a Base
alone, since it always needs a Derived type to be known.
Because of the particular construction of CRTP, all the classical OOP stuff leads to a sort of "indifferent equilibrium", since there is no more the "dangerous usecase".
You can use them or not, but no particular bad-thing can happen. Not if you use object the way they had been designed to be used.