Here are some ideas that could help if you can't use the PIMPL solution from the other answer. They are either based on CRTP or on constructor tagging:
#include <type_traits>
struct A;
template<typename T>
struct Base final {
};
// Specialise Base for A to be non-final
template<>
struct Base<A> {};
struct A : Base<A> {};
The idea behind the first snippet is to have a final
CRTP-base and add a non-final specialisation for class A
. Obviously this allows users (or yourself) to define more allowed derivations if needed, but prevents accidental inheritance.
template<char...>
struct password {};
struct Base2 {
Base2() = delete;
template<char... pwd>
Base2(password<pwd...> ) {
using expected_pwd = password<'t','o','p','s','e','c','r','e','t'>;
static_assert(std::is_same_v<password<pwd...>, expected_pwd>);
}
};
struct A2 : Base2 {
A2() : Base2(password<'t','o','p','s','e','c','r','e','t'>{}) {}
};
struct A3 : Base2 {
// No way to construct Base2 without correct password :)
A3() : Base2(password<'b', 'a', 'm', 'm'>{}) {}
};
The idea behind this second snippet (which is more a joke than real code...) is to replace the default constructor of the Base2
class by a password
struct. Within the implementation you check the password given to the constructor. In the implementation of your derived class you simply call the Base2
constructor with the correct password. Of course, your users can derive from the base class but as the password is completely hidden in the implementation, there is no way to construct the derived objects :)
int main(){
auto a = A{};
auto a2 = A2{};
auto a3 = A3{}; // fails
return 0;
}