1

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?

9
  • A little bit offtopic, but to have 0x1000 size you will need not 4, but 7-8 arrays of size 0x250. Back on topic, did you tried any other compilers? PS: Are you sure you really need constexpr array? Because it seems that just const, initialized in runtime, should suffice. Commented Jul 18, 2019 at 6:09
  • @sklott Whoops, forgot I was working with hexadecimals. The code compiles just fine using g++. Yes, I need a constexpr because otherwise I wouldn't be able to store func<x> in my array (afaik). Commented Jul 18, 2019 at 6:20
  • Which version of C++ is this? In C++20 array::fill is constexpr, so you use it instead. Commented Jul 18, 2019 at 6:28
  • @RadosławCybulski Array fill uses one value for the whole array. I want the passed functions to be different based on the current loop index, hence func<x>. Commented Jul 18, 2019 at 6:32
  • 1
    Ok, i see now. It does compile with clang as well. Maybe change your static_for implementation to use tree structure? Recursively split range in half and execute left, then right, until range is of size one, where you execute the function itself? Commented Jul 18, 2019 at 6:36

2 Answers 2

1

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>();
Sign up to request clarification or add additional context in comments.

3 Comments

It works perfectly, thank you. I really have to dive deeper into template programming.
@jsmolka - I suppose it's because, this way, you avoid recursion. As far I know, compilers usually have configurable but strict limits to number of template recursion.
Instead of IntExecutor should I use the name of type like uint16_t, isn't it? How to make IntExecutor also flexible?
0

I've modified the code max66 posted a little to allow constexpr array creation using a custom lamdba function. I am just pasting it here in case anybody needs it.

#include <array>
#include <iostream>

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

using IntExecutor = Executor<int>;

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

template<int value>
constexpr IntExecutor emitExecutor()
{
    return exec<value>;
}

template<typename T, class Func, std::size_t ...Is>
constexpr std::array<T, sizeof...(Is)> makeArrayImpl(Func&& func, std::index_sequence<Is...>)
{
    return { func(std::integral_constant<std::size_t, Is>{})... };
}

template<typename T, std::size_t N, class Func>
constexpr std::array<T, N> makeArray(Func&& func)
{
    return makeArrayImpl<T>(std::forward<Func>(func), std::make_index_sequence<N>{});
}

constexpr auto executors = makeArray<IntExecutor, 0x1000>([&](auto x) {
    return emitExecutor<static_cast<int>(x)>();
});

int main(int argc, char* argv[])
{
    for (const auto& executor : executors)
    {
        executor(10);
    }
    return 0;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.