Created
July 14, 2022 14:15
-
-
Save jcarrano/39f5acbf3d94aeed802bda15aa7e79f9 to your computer and use it in GitHub Desktop.
C++ Class Registry
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Create a registry of classes | |
* Original idea: https://stackoverflow.com/questions/34858341/c-compile-time-list-of-subclasses-of-a-class | |
* also see https://stackoverflow.com/questions/72884647/when-are-static-member-variables-optimized-away | |
* for info about the subtleties of static members. | |
*/ | |
#include <functional> | |
#include <iostream> | |
#include <map> | |
#include <typeinfo> | |
#include <vector> | |
/** | |
* Base class for Class Registries. | |
* | |
* Subclasses must implement a protected static member template: | |
* `template <typename Sub> static void registerCls();` | |
* | |
* What this method does and how it adds the entries to the container is up to | |
* the subclass to implement. | |
*/ | |
template <typename Container, typename CRTP> class ClassRegistry { | |
public: | |
using ContainerT = Container; | |
static const ContainerT& classes(void) { return _classes(); } | |
/** | |
* Register a class in a container contained in a collector class. | |
* | |
* By declaring a static member using this template, the containing class will | |
* be added to a container at startup. | |
* | |
* @param Collector Class that contains the registry (a static member vector | |
* named `_classes`). It is not necessary for this class | |
* to be a superclass of SubC. | |
* @param SubC Class to register (you must use CRTP). | |
*/ | |
template <class SubC> class ClassRegistrator { | |
public: | |
ClassRegistrator() | |
{ | |
ClassRegistry::AccessRegisterImpl::template registerCls<SubC>(); | |
} | |
}; | |
protected: | |
template <class SubC> friend class ClassRegistrator; | |
/** | |
* Helper to allow subclasses to have registerCls as a protected member | |
*/ | |
struct AccessRegisterImpl : CRTP { | |
template <typename Sub> static void registerCls() | |
{ | |
CRTP::template registerCls<Sub>(); | |
} | |
}; | |
// By using a static method instead of a member we avoid problems with | |
// initialization order (i.e. using the container before it is initialized). | |
static ContainerT& _classes(void) | |
{ | |
static ContainerT c {}; | |
return c; | |
} | |
}; | |
/** | |
* Mixin that registers the class. | |
* | |
* When this template is instantiated, the derived class "CRTP" will be | |
* registered in RegistryClass. | |
*/ | |
template <typename RegistryClass, typename CRTP> class WithRegistrator { | |
public: | |
using RegistratorT = typename RegistryClass::template ClassRegistrator<CRTP>; | |
#ifdef __clang__ | |
// The used attribute in the static member variabled does not work in clang so | |
// we need this. | |
static RegistratorT& _clang_workaround(void) __attribute__((used)) | |
{ | |
return registrator; | |
} | |
#endif | |
private: | |
static inline RegistratorT registrator __attribute__((used)) {}; | |
}; | |
/* ************************************************************************** | |
* Example usage | |
***************************************************************************/ | |
class BaseR : public ClassRegistry<std::map<const char*, void (*)(int x)>, BaseR> { | |
protected: | |
template <typename Sub> static void registerCls() | |
{ | |
_classes().emplace(Sub::name, &Sub::do_thing); | |
} | |
}; | |
template <typename T> class Base : public WithRegistrator<BaseR, T> { | |
}; | |
class A : public Base<A> { | |
public: | |
static inline const char* name = "a"; | |
static void do_thing(int x) { std::cout << "I'm class A"; } | |
}; | |
class B : public Base<B> { | |
public: | |
static inline const char* name = "b"; | |
static void do_thing(int x) { std::cout << "I'm class B"; }; | |
}; | |
int main() | |
{ | |
for (auto t : BaseR::classes()) { | |
std::cout << t.first; | |
t.second(78); | |
std::cout << "\n"; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment