user6846474
user6846474

Reputation:

Collecting information on which template variants are being instantiated in a program

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 Foos 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.

Upvotes: 3

Views: 103

Answers (3)

user6846474
user6846474

Reputation:

OK, because the solution in my question was only partial, I'll share a working code snippet that actually shows what I was after, now that I figured it out.

We don't need any compiler-specific hacks, the following should work universally:

#include <iostream>
#include <vector>
#include <string>
#include <mutex>
#include <boost/type_index.hpp>

static std::vector<std::string>& getInstantiations() {
    static std::vector<std::string> instantiations;
    return instantiations;
}

static std::mutex& getMutex() {
    static std::mutex mut;
    return mut;
}

template <class T>
struct Introspection {
    Introspection() { 
        std::lock_guard<std::mutex> lock(getMutex());
        getInstantiations().push_back(boost::typeindex::type_id<T>().pretty_name());
    }
    void forceExistence() {}
};


template <class... Ts>
struct Foo {
    Foo() { // This never gets called and we want that
        std::cout << "Constructed Foo!" << std::endl;
        introspection.forceExistence();
    }
private:
    static Introspection<Foo<Ts...>> introspection;
};

template <class... Ts>
Introspection<Foo<Ts...>> Foo<Ts...>::introspection;

void unused() {
    Foo<char> c;
    Foo<char> c2;
    Foo<double> d;
    Foo<int, const int> i;
    Foo<float, bool, long> f;
}

int main() {
    for (auto& i : getInstantiations()) {
        std::cout << i << std::endl;
    }

    /*
        output:

        Foo<char>
        Foo<double>
        Foo<int, int const>
        Foo<float, bool, long>
    */
}

It looks silly but consider a larger project with Foo<...> declarations all over the place. Yes, maybe I could just use a regex search but this way I can actually work with the gathered information while the inspected program is running. Echoing the type names is just the simplest example of what we could do with this.

Upvotes: 0

Richard Hodges
Richard Hodges

Reputation: 69864

It can be achieved with no hacks and no need for an unused() function if we use a variation of the schwartz counter idiom:

global_counter.hpp

#pragma once

struct GlobalCounter {
    int next();
    int value() const;
    int value_ = 0;
    struct init {
        init();
        ~init();
    };
};

extern GlobalCounter& globalCounter;
static GlobalCounter::init globalCounterInit;

global_counter.cpp

#include "global_counter.hpp"

#include <memory>
#include <type_traits>

static int globalCounterCount;
static std::aligned_storage_t <sizeof(GlobalCounter), alignof(GlobalCounter)> globalCounterStorage;
GlobalCounter& globalCounter = reinterpret_cast<GlobalCounter&>(globalCounterStorage);

GlobalCounter::init::init() {
    if(globalCounterCount++ == 0) new (&globalCounter) GlobalCounter ();
}

GlobalCounter::init::~init() {
    if (--globalCounterCount == 0) globalCounter.~GlobalCounter();
}

int GlobalCounter::next() {
    return value_++;
}

int GlobalCounter::value() const {
    return value_;
}

foo.hpp

#pragma once

#include "global_counter.hpp"
#include <iostream>

template <class T>
struct Foo {
    Foo() { // This never gets called and we want that
        std::cout << "Constructed Foo!" << std::endl;
    }

    static int ident;
};

template<class T>
int Foo<T>::ident;

template<class T>
struct EnableFoo
{
    EnableFoo()
    {
        if (counter++ == 0)
            Foo<T>::ident = globalCounter.next();
    }

    static int counter;
};
template<class T> int EnableFoo<T>::counter;



static EnableFoo<char> enableFooChar;
static EnableFoo<int> enableFooInt;
static EnableFoo<double> enableFooDouble;
static EnableFoo<float> enableFooFloat;

main.cpp

#include <iostream>
#include "foo.hpp"

int main() {

    std::cout << globalCounter.value() << std::endl; // prints 4
    return 0;
}

See schwartz counter:

https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter

Upvotes: 1

Ryan Haining
Ryan Haining

Reputation: 36792

If you're willing to add the cost of an extra variable to your objects, this seems to do what you want, though this is all very complicated and I could be missing a case:

#include <iostream>

static int& gInt() { // Global counter
    static int i;
    return i;
}

struct Initializer {
    Initializer() { ++gInt(); }
};


template <class T>
struct Foo {
    Foo() { // This never gets called and we want that
        std::cout << "Constructed Foo!" << std::endl; 
    }
  private:
    static Initializer gint_incrementer;
    void* p_ = &gint_incrementer; // force existence 
};

template <typename T>
Initializer Foo<T>::gint_incrementer;

void unused() {
    Foo<char> c;
    Foo<char> c2;
    Foo<double> d;
    Foo<int> i;
    Foo<float> f;
}

int main() {
    std::cout << gInt() << std::endl; // prints 4
}

Upvotes: 3

Related Questions