Skip to content

Instantly share code, notes, and snippets.

@jcarrano
Created July 14, 2022 14:15
Show Gist options
  • Save jcarrano/39f5acbf3d94aeed802bda15aa7e79f9 to your computer and use it in GitHub Desktop.
Save jcarrano/39f5acbf3d94aeed802bda15aa7e79f9 to your computer and use it in GitHub Desktop.
C++ Class Registry
/* 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