As others have pointed out in the comments to your question, in general it is not possible to detect all existing derived classes of a particular base class at compile time with C++.
But if all you need is a mechanism to avoid having one single place knowing about all existing derived classes there is something you can do, albeit not at compile time.
Basic idea
The idea is to use initialization of static member variables (which is guaranteed to happen before main is executed) to register the derived classes in a common registry.
Such a registry can look like this:
class derived_registry
{
public:
static std::size_t number_of_instances()
{
return _instances.size();
}
static base* instance(std::size_t const index)
{
assert(index < _instances.size());
return _instances[index].get();
}
template <typename T, std::enable_if_t<std::is_base_of_v<base, T>, int> = 0>
static std::size_t register_derived_class(std::unique_ptr<T> instance)
{
auto const index = _instances.size();
_instances.emplace_back(std::move(instance));
return index;
}
private:
static std::vector<std::unique_ptr<base>> _instances;
};
inline std::vector<std::unique_ptr<base>> derived_registry::_instances;
Any derived class of base now has to register itself by calling derived_registry::register_derived_class to initialize a static member variable, e.g. like so:
// in derived1.h
class derived1 : public base
{
public:
derived1();
void do_something() override;
private:
static std::size_t _index;
};
// in derived1.cpp
std::size_t derived1::_index = derived_registry::register_derived_class(std::make_unique<derived1>());
derived1::derived1()
: base{}
{
}
void derived1::do_something()
{
std::cout << "derived 1\n";
}
This also shows why it is important to define the derived_registry::_instances vector as an inline variable: We need to be sure that derived_registry::register_derived_class is only called after derived_registry::_instances is already initialized. The easiest way to do so is to use the fact that when there is more than one variable with static storage duration defined in one translation unit they are guaranteed to be initialized in the order in which they were defined. Since we've defined derived_registry::_instances in the header file, we're guaranteed that derived_registry::_instances is initialized before derived1::_index, and thus before the call of derived_registry::register_derived_class.
You can see this approach in action on wandbox.
Making it fool-proof
While the implementation above works, it is rather cumbersome and there still is the possiblity that someone adds a new derived class but forgets to register it.
To ease the registration part you could use the CRTP pattern as described in the answer to this question that StoryTeller linked in a comment to your question.
While this can make the registration significantly easier, the registry still only works correctly when every derived class either inherits from the CRTP base or implements the registration itself, which is easy to forget. To ensure that there is no other choice but to inherit from the CRTP base class you can additionally make the constructor of base private and make the CRTP base-class the only friend. Then one can't accidentally inherit directly from base without registering the new derived class.