C++ Type Registration at Compile Time Trick

C++ type registration at compile time trick

There is a way to register types one by one and then retrieve all of them in the form of mpl::vector or similar. I've learned this trick on the boost mailing lists (perhaps from Dave Abrahams, although I can't recall for sure).

Edit: I learned it from slide 28 on https://github.com/boostcon/2011_presentations/raw/master/thu/Boost.Generic.pdf.

I won't use MPL in the code to make it self contained.

// The maximum number of types that can be registered with the same tag.
enum { kMaxRegisteredTypes = 10 };

template <int N>
struct Rank : Rank<N - 1> {};

template <>
struct Rank<0> {};

// Poor man's MPL vector.
template <class... Ts>
struct TypeList {
static const int size = sizeof...(Ts);
};

template <class List, class T>
struct Append;

template <class... Ts, class T>
struct Append<TypeList<Ts...>, T> {
typedef TypeList<Ts..., T> type;
};

template <class Tag>
TypeList<> GetTypes(Tag*, Rank<0>) { return {}; }

// Evaluates to TypeList of all types previously registered with
// REGISTER_TYPE macro with the same tag.
#define GET_REGISTERED_TYPES(Tag) \
decltype(GetTypes(static_cast<Tag*>(nullptr), Rank<kMaxRegisteredTypes>()))

// Appends Type to GET_REGISTERED_TYPES(Tag).
#define REGISTER_TYPE(Tag, Type) \
inline Append<GET_REGISTERED_TYPES(Tag), Type>::type \
GetTypes(Tag*, Rank<GET_REGISTERED_TYPES(Tag)::size + 1>) { \
return {}; \
} \
static_assert(true, "")

Usage example:

struct IntegralTypes;
struct FloatingPointTypes;

// Initially both type lists are empty.
static_assert(std::is_same<GET_REGISTERED_TYPES(IntegralTypes), TypeList<>>::value, "");
static_assert(std::is_same<GET_REGISTERED_TYPES(FloatingPointTypes), TypeList<>>::value, "");

// Add something to both lists.
REGISTER_TYPE(IntegralTypes, int);
REGISTER_TYPE(FloatingPointTypes, float);
static_assert(std::is_same<GET_REGISTERED_TYPES(IntegralTypes), TypeList<int>>::value, "");
static_assert(std::is_same<GET_REGISTERED_TYPES(FloatingPointTypes), TypeList<float>>::value, "");

// Add more types.
REGISTER_TYPE(IntegralTypes, long);
REGISTER_TYPE(FloatingPointTypes, double);
static_assert(std::is_same<GET_REGISTERED_TYPES(IntegralTypes), TypeList<int, long>>::value, "");
static_assert(std::is_same<GET_REGISTERED_TYPES(FloatingPointTypes), TypeList<float, double>>::value, "");

Print a type's name at compile time without aborting compilation?

The following mechanism is due to @JonathanWakely, and is specific to GCC:

int i;

template <typename T>
[[gnu::warning("your type here")]]
bool print_type() { return true; }

bool b = print_type<decltype(i)>();

This gives you:

<source>:In function 'void __static_initialization_and_destruction_0(int, int)':
<source>:7:33: warning: call to 'print_type<int>' declared with attribute warning: your
type here [-Wattribute-warning]
7 | bool b = print_type<decltype(i)>();
| ~~~~~~~~~~~~~~~~~~~~~~~^~

See it working on Godbolt.

Getting type names at compile time in C++

I can't see typeid(T).name() incurring a runtime overhead. typeid(expr) yes, if expr is of a polymorphic type.

It looks like the demangling probably happens at runtime, but there's not an awful lot you can do about that. If this is only for debugging then I really wouldn't worry about it too much unless your profiler indicates that this is causing your program to slow down so much that debugging other elements of it is troublesome.

C++ compile-time and runtime available name attribute

Unfortunately, you cannot declare a static method virtual so something like this wouldn't work :

struct BaseClass {
static virtual std::string name() =0;
};

struct ChildClass {
static std::string name() {
return "my_name";
}
};

you can instead make a method with a slightly different name :

struct BaseClass {
virtual std::string name() =0;
};

struct ChildClass {
std::string name() {
return ChildClass::_name();
};

static std::string _name() {
return "my_name";
}
};

which will work with the three cases you describe :

ChildClass c;
BaseClass& b = c;
std::cout << ChildClass::_name() << std::endl;
std::cout << c.name() << std::endl;
std::cout << b.name() << std::endl;

Dynamically register constructor methods in an AbstractFactory at compile time using C++ templates

Answer One

The general technique of deriving a class like this is the Curiously Recurring Template Pattern (CRTP):

class PingMessage: public MessageTmpl < 10, PingMessage > 

Your specific technique of using a template class's static member initialization to register subclasses of that class is (IMO) simply brilliant, and I've never seen that before. A more common approach, used by unit test frameworks like UnitTest++ and Google Test, is to provide macros that declare both a class and a separate static variable initializing that class.

Answer Two

Static variables are initialized in the order listed. If you move your m_List declaration before your MessageFactory::Register calls, you should be safe. Also keep in mind that if you start declaring Message subclasses in more than one file, you'll have to wrap m_List as a singleton and check that it's initialized before each use, due to the C++ static initialization order fiasco.

Answer Three

C++ compilers will only instantiate template members that are actually used. Static members of template classes is not an area of C++ that I've used much, so I could be wrong here, but it looks like providing the constructor is enough to make the compiler think that MESSAGE_ID is used (thus ensuring that MessageFactory::Register is called).

This seems very unintuitive to me, so it may be a compiler bug. (I was testing this in g++ 4.3.2; I'm curious to know how Comeau C++, for example, handles it.)

Explicitly instantiating MESSAGE_ID also suffices, at least in g++ 4.3.2:

template const uint16_t PingMessage::MESSAGE_ID;

But that's even more unnecessary work than providing an empty default constructor.

I can't think of a good solution using your current approach; I'd personally be tempted to switch to a technique (such as macros or using a script to generate part of your source files) that relied less on advanced C++. (A script would have the added advantage of easing maintenance of MESSAGE_IDs.)

In response to your comments:

Singletons are generally to be avoided because they're often overused as poorly disguised global variables. There are a few times, however, when you really do need a global variable, and a global registry of available Message subclasses is one of those times.

Yes, the code that you provided is initializing MESSAGE_ID, but I was talking about explicitly instantiating each subclass's instance of MESSAGE_ID. Explicit instantiation refers to instructing the compiler to instantiate a template even if it thinks that that template instance won't otherwise be used.

I suspect that the static function with the volatile assignment is there to trick or force the compiler into generating the MESSAGE_ID assignment (to get around the problems that dash-tom-bang and I pointed out with the compiler or linker dropping or not instantiating the assignment).



Related Topics



Leave a reply



Submit