Search code examples
c++templatesenumscompile-time

Is it impossible to pass a run-time integer as a template argument?


I have a base class and a class template, and I want to be able to instantiate it as follows:

class Base
{
};



template<int i>
class Foo
{
};

namespace SomeEnum
{
enum
{
    First,
    Second,
    Third,
    ...
    Last
};
}

void bar()
{
    std::unique_ptr<Base> ptr{ nullptr };
    int fooType = rand() % SomeEnum::Last;

    ptr = std::make_unique<Foo<fooType>> // Causes error because I'm passing a run-time value to something that expects a compile-time value
}

The enum used to be a class enum but I changed it to a regular enum so that I could use the design presented above, since the enum in question is quite large. I thought I was being clever at the time but I now realize I had kind of forgotten that templates are handled at compile-time.

Is there any way to circumvent this, without completely changing my design? I could obviously do something like this:

switch(fooType)
{
case 0:
    ptr = std::make_unique<Foo<0>>();
    break;
case 1:
    ptr = std::make_unique<Foo<1>>();
    break;
...
}

But it doesn't look very elegant and pretty much removes the motivation for not just using a class enum since I will have to make a switch case for every value in the enum. Is there any other solution, or have I painted myself into a corner with this design?


Solution

  • A simple solution would be this:

    namespace detail {
        template<size_t I>
        std::unique_ptr<Base> makeForIndex() {
            return std::make_unique<Foo<I>>();
        }
    
        template<size_t... Is>
        auto makeFoo(size_t nIdx, std::index_sequence<Is...>) {
            using FuncType = std::unique_ptr<Base>(*)();
            constexpr FuncType arFuncs[] = { 
                detail::makeForIndex<Is>...
            };
            return arFuncs[nIdx]();
        }
    }
    
    auto makeFoo(size_t nIdx) {
        return detail::makeFoo(nIdx, std::make_index_sequence<SomeEnum::Last>());
    }
    

    This would not require template recursion either and is rather easy to understand. We create an array of function pointers and index into that using the runtime value provided by the caller.

    Now you can create your Foo<n> like

    size_t n;
    ...
    auto ptr = makeFoo(n);