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
Why Is the Volatile Qualifier Used Through Out Std::Atomic
Thread Safe Lazy Construction of a Singleton in C++
C++11: Number of Variadic Template Function Parameters
Is There Any Alternative to Using % (Modulus) in C/C++
What Is the Default Hash Function Used in C++ Std::Unordered_Map
When Should Your Destructor Be Virtual
Why Is 'Std::Initializer_List' Often Passed by Value
Practical Uses for the "Curiously Recurring Template Pattern"
Using Hierarchy in Findcontours () in Opencv
Should I Use Shared_Ptr or Unique_Ptr
Get Current Time in Milliseconds Using C++ and Boost
Visual Studio: Run C++ Project Post-Build Event Even If Project Is Up-To-Date
When Is #Include <New> Library Required in C++
Is "Inline" Implicit in C++ Member Functions Defined in Class Definition
C++: How to Implement Polymorphic Object Creator to Populate a Table