Question

Say I want to be able to create objects for a range of template parameter

template<int a> class Myclass : public MyclassSuper{...};

And I want to create objects like

Myclass<runtime_dependent_int> a(b);

Now this is not allowed because runtime_dependent_int is not known at compile time.

How can I circumvent this limitation systematically? I was thinking maybe something Factory-like?

class MyclassFactory {

  static MyclassSuper* createObject(const int runtime_dependent_int, constructor_args b){...}
  ...
};

The easy and ugly way would be just a big switch-case inside createObject which goes through the whole set of ints I am interested in and casts

Myclass<runtime_dependent_int>* 

into

MyclassSuper*

Maybe it can be done better, with for-loop or some clever template meta-magic?

Was it helpful?

Solution

A switch-case statement isn't that bad: It is easy to implement, easy to understand, and easy to maintain if the number of allowed values is limited (say less than 30). However, if the number of allowed values is large, the switch-case statement might become a beast that is thousands of lines long.

One way to get rid of such a switch-case statement is using a std::map with creator functions. Consider the following:

struct FooBase
{};

template <int N>
struct Foo : public FooBase
{};

using FooCreator = std::function<std::unique_ptr<FooBase>()>;

std::map<int, FooCreator> initializeFooCreatorMap();

std::unique_ptr<FooBase> createFoo(int const n)
{
    static std::map<int, FooCreator> const creatorMap = initializeFooCreatorMap();
    auto const it = creatorMap.find(n);
    if (it != creatorMap.end())
    {
        return it->second();
    }
    return nullptr;
}

Here, the switch-case statement was replace with a map lookup. Performance-wise this isn't too bad: The complexity of std::map::find is logarithmic, so searching a map with 2^32 elements takes about 32 comparisons. The interesting part here is how initializeFooCreatorMap() is implemented. Unfortunately, you have to instantiate the template with all allowed integer values. The easiest way is to hardcode

m.emplace(n, [](){ return std::unique_ptr<Foo<n>>(); });

for all values of n into initializeFooCreatorMap(). However, this isn't much better than a switch-case statement, since you still need one line of code for each allowed value.

You can use template recursion to simplify this. For example, if you want to instantiate the templates for a fixed list of integers, you can do the following:

template <int N, int... Rest>
struct FooCreatorInserter
{
    static void insert(std::map<int, FooCreator>& creatorMap)
    {
        FooCreatorInserter<N>::insert(creatorMap);
        FooCreatorInserter<Rest...>::insert(creatorMap);
    }
};

template <int N>
struct FooCreatorInserter<N>
{
    static void insert(std::map<int, FooCreator>& creatorMap)
    {
        creatorMap.emplace(N, []() { return std::make_unique<Foo<N>>(); });
    }
};

std::map<int, FooCreator> initializeFooCreatorMap()
{
    std::map<int, FooCreator> creators;
    FooCreatorInserter<2, 3, 5, 7, 11, 13, /*...*/>::insert(creators);
    return creators;
}

Instead of maintaining a large switch-case statement, you only need to maintain a single template argument list, namely FooCreatorInserter<2, 3, 5, 7, 11, 13, /*...*/>.

If you cannot use variadic template arguments, you can use other template recursion tricks to fill the list. For example, you can use the following to initialize the creator map with all integers from 100 to 200:

template <int N>
struct RecursiveFooCreatorInserter
{
    static void insert(std::map<int, FooCreator>& creatorMap)
    {
        creatorMap.emplace(N, [](){ return std::make_unique<Foo<N>>(); });
        RecursiveFooCreatorInserter<N - 1>::insert(creatorMap);
    }
};

template <>
struct RecursiveFooCreatorInserter<100>
{
    static void insert(std::map<int, FooCreator>& creatorMap)
    {
        creatorMap.emplace(100, []() { return std::make_unique<Foo<100>>(); });
    }
};

std::map<int, FooCreator> initializeFooCreatorMap()
{
    std::map<int, FooCreator> creators;
    RecursiveFooCreatorInserter<200>::insert(creators);
    return creators;
}
Licensed under: CC-BY-SA with attribution
scroll top