Search code examples
c++variadic-templatestemplate-meta-programmingconstexprstdarray

Initialize constexpr array with template functions


I am trying to create a constexpr std::array with precompiled handler functions for my emulator. The code below works just fine for smaller numbers like 0x250, but everything above causes a 'C1026 parser overflow, program too complex' when used with the recent version of MSVC.

#include <array>
#include <iostream>

template<typename T>
using Executor = void(*)(T);

using IntExecutor = Executor<int>;

template<int arg>
void func(int value)
{
    std::cout << (arg * value) << std::endl;
}

// Static for https://codereview.stackexchange.com/a/173570/160845

template<typename T, T Begin, class Func, T ...Is>
constexpr void static_for_impl(Func&& f, std::integer_sequence<T, Is...>)
{
    (f(std::integral_constant<T, Begin + Is>{ }), ...);
}

template<typename T, T Begin, T End, class Func>
constexpr void static_for(Func&& f)
{
    static_for_impl<T, Begin>(std::forward<Func>(f), std::make_integer_sequence<T, End - Begin>{ });
}

template<int N>
constexpr std::array<IntExecutor, N> makeLut()
{
    std::array<IntExecutor, N> lut = { };
    static_for<size_t, 0, N>([&](auto x) {
        lut[x] = func<x>;
    });
    return lut;
}

// 0x250 works just fine
// 0x300 causes a "C1026 parser overflow, program too complex" error
constexpr auto lut = makeLut<0x250>();

int main(int argc, char* argv[])
{
    int instruction = 0xDEADBEEF;

    int instructionHash = instruction & 0x24F;

    lut[instructionHash](instruction);

    return 0;
}

I need an std::array with the size of 0x1000. I can achieve that by using 4 smaller static_for() loops from 0 to 0x250, but I feel like that's an ugly solution.

Does anybody know a proper way to fill an constexpr std::array with template functions?


Solution

  • Have you tried the solution based over std::make_index_sequence/std::index_sequence ?

    template <std::size_t ... Is>
    constexpr std::array<IntExecutor, sizeof...(Is)> 
       makeLutHelper (std::index_sequence<Is...>)
     { return { func<int(Is)>... }; }
    
    template <std::size_t N>
    constexpr auto makeLut ()
     { return makeLutHelper(std::make_index_sequence<N>{}); }
    

    I can't test it with MSVC but, in my Linux platform, g++ and clang++ compile also (with long, long time)

    constexpr auto lut = makeLut<0x10000u>();