simulate
simulate

Reputation: 1282

Generating code (at compile-time) to call a static function of every instantiation of a template

My problem could be described with the example below.

I want to call a static function from each instantiation of a template. What I am thinking of is something like a tuple which gets expanded each time a new instantiation of a template is encountered, so that I can access that tuple recursively and call a static function of each of the types in the tuple.

#include <vector>

template<typename T>
    struct Junk
    {
        static std::vector<T> all;
        Junk(T t)
        {
            all.push_back(t);
        }

        static void clear()
        {
            all.clear();
        }
    };

template<typename T>
    std::vector<T> Junk<T>::all;

void clearJunk()
{
    Junk<float>::clear();
    Junk<char>::clear();
    Junk<unsigned int>::clear();        
    // how can I generalize this?
}

int main()
{
    // instantiate Junk with different types
    Junk<float> f1(1.0f);
    Junk<float> f2(2.0f);
    // Junk<float>::all.size() == 2

    Junk<char> c1('a');
    Junk<char> c2('b');
    Junk<char> c3('c');
    // Junk<char>::all.size() == 3

    Junk<unsigned int> i1(1);
    // Junk<unsigned int>::all.size() == 1

    // clear all Junk
    clearJunk();

    return 0;
}

A run-time solution to this would be a vector of function pointers, and the first object of each template instantiation pushes a pointer to the static member function to that vector:

std::vector<void(*)()> clear_funcs;

template<typename T>
    struct Junk
    {
        static std::vector<T> all;
        Junk(T t)
        {
            (void)init;   // mention init at least once so its constructor will be invoked
            all.push_back(t);
        }

        static void clear()
        {
            all.clear();
        }
        struct At_Init
        {
            At_Init()
            {
                clear_funcs.push_back(&Junk<T>::clear);
            }
        };
        static At_Init init; // will only be constructed once for each template instantiation
    };

template<typename T>
    std::vector<T> Junk<T>::all;

template<typename T>
    typename Junk<T>::At_Init Junk<T>::init;

void clearJunk()
{
    // call every clear() of all the Junk template instantiations
    for (void(*clear)() : clear_funcs) {
        clear();
    }
}

But this solution is not as fast as a compile time solution would be and it requires a lot of code in the class that is not at all meaningful, so I dislike this solution, although it works.

How could you generate code at compile-time to call a static function of every instantiation of a template?

Upvotes: 0

Views: 145

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275780

You are out of luck.

To see why, imagine there are 3 dynamic libraries. They each use a Junk<typeX> where X is 0, 1 or 2.

These libraries are loaded after the program runs, depending on the phase of the moon.

No central location can possibly know which Junk<?>::clear() methods to invoke at compile time. In order to know which methods to invoke, you'd have to have a central location responsible for it and track at runtime which Junk<?> types are instantiated.

Now, you may not be using dynamic libraries, but the fact that the language (in practice) supports this means that there isn't a way to track cross compilation units an enumeration of all types instantiated from a template without storing it as runtime state. At compile time, each compilation unit (cpp file) can be compiled separately.

Naturally there are ways around this; if you had only one compilation unit (or even a unity build), or if you maintained a central list of types supported (and optionally generated hard compile time errors if you missed types), you could generate code like your static code case.

But before you do that, profile your simple dynamic solution and ensure it is an actual problem.

Upvotes: 3

Related Questions