Today I learned that when we have a C++ class template with a static member variable, its constructor won't be called (in fact the member won't even be defined) unless we "use it in a way that requires the definition of the static data member to exist".
This phenomenon is very nicely explained here: C++ Static member initalization (template fun inside)
In practice, it means we have to explicitly refer to each instantiation of that static member (from outside the class template) if we want initialization (and any possible side effects of it) to take place.
I've been thinking about ways to overcome this issue.
My motivation is that there's an existing code base which uses various instantiations of class template Foo
(it has multiple template parameters but I simplified that for the sake of the example) and I would like to automatically collect information about all the different parameter combinations.
I cannot practically wait for all these Foo
s to be constructed during program execution (it's a long running background process) so I thought that I could put a static Initializer<T>
inside Foo<T>
and have it extract the desired type information for each distinct Foo
type right after the program starts.
In this case, having to enumerate all the instantiations of Initializer<T> Foot<T>::init
in order to have the initializers run in the first place obviously defeats the purpose. I would have to go and see (all over the project) what the types are and that is precisely what I'm trying to automate.
I noticed that if I replace static member variable with static method holding a local static Initializer
instance, I can force generation of Initializer<T>
definitions more easily. I just have to take a pointer to that method (e.g. inside Foo
's constructor).
The last step is to call this static method after the program starts. In case of g++/clang, using __attribute__((constructor))
works like a charm.
I also have to deal with MSVC++ though and this is what I came up with:
#include <iostream>
#if defined(_MSC_VER) && !defined(__clang__)
#define MSVC_CONSTRUCTOR_HACK
#define ATTRIBUTE_CONSTRUCTOR
#else
#define ATTRIBUTE_CONSTRUCTOR __attribute__((constructor))
#endif
static int& gInt() { // Global counter
static int i;
return i;
}
template <class T>
struct Initializer {
// If it works, this gets called for each Foo<T> declaration
Initializer() {
gInt()++;
}
};
#ifdef MSVC_CONSTRUCTOR_HACK
__pragma(section(".CRT$XCU", read))
template <class T> // This will hold pointers to Foo<T>::getInit
static void(*g_constructors__)(void);
#endif
template <class T>
struct Foo {
ATTRIBUTE_CONSTRUCTOR // Empty in case of MSVC
static void getInit() {
static Initializer<T> init;
}
#ifdef MSVC_CONSTRUCTOR_HACK
template <> // Why is this allowed?!
__declspec(allocate(".CRT$XCU")) static void(*g_constructors__<T>)(void) = getInit;
#endif
Foo() { // This never gets called and we want that
std::cout << "Constructed Foo!" << std::endl;
(void)&getInit; // This triggers instantiation and definition of Initializer<T>
}
};
void unused() {
Foo<char> c;
Foo<double> d;
Foo<int> i;
Foo<float> f;
}
int main() {
std::cout << gInt() << std::endl; // prints 4
return 0;
}
It relies on putting function pointers into the .CRT section of the executable (https://stackoverflow.com/a/2390626/6846474, https://github.com/djdeath/glib/blob/master/glib/gconstructor.h).
In order to make it work in this context, though, I also had to resort to this really strange hack: There's a global variable template g_constructors__
which is explicitly specialized inside (!) Foo
.
To be honest, I was really surprised this works. I know it is non-standard but can someone explain how is it that it even compiles? Is it just luck or is it somehow "well-formed" at least as far as Microsoft C++ goes?
I am aware I could do this using some kind of external static analysis tool instead, but this is pretty close to what I want with the major advantage that it's all incorporated into the program being inspected.
If I can invoke Initializer for each T (and it looks like I can), extracting the type info is easy. I can stringify the template parameters using boost typeindex or anything else I need. The global counter was used here only to see if Initializer
instances are being created or not.