Reputation: 1914
I am working on a GameBoy emulator. Out of curiosity, I want to generate an array of constexpr
Opcode
objects that contain a run()
function pointer among some other useful fields.
Here's an example of how it roughly would work like:
#include <array>
struct CPU
{
int some_state = 0;
};
void f(CPU& cpu)
{
cpu.some_state = 42;
}
void g(CPU& cpu)
{
cpu.some_state = 12;
}
struct Opcode
{
using Runner = void(*)(CPU&);
Runner run = [](CPU&) {};
/* more fields here */
};
constexpr auto gen_opcodes()
{
std::array<Opcode, 2> ret {};
ret[0] = { f };
ret[1] = { g };
return ret;
}
constexpr auto opcodes = gen_opcodes();
int main()
{
CPU cpu;
opcodes[1].run(cpu);
}
The reason for the array to be constexpr
is that I want it to be well optimized by the compiler. In my understanding, if the array was only const
it would be way harder for the compiler to optimize away the run()
calls, as those are called individually in such a way the compiler should inline them, i.e. if (something == 0x00) { opcodes[0x00].run(blahblah); }
.
However, the interest of this approach is to generate a bunch of opcodes
at once. I thought of using template functions, as some opcodes
come into a pattern, I should easily be able to generate tens of opcodes
at once!
But while the following works:
template<int i>
void f(CPU& cpu)
{
cpu.some_state = i;
}
/* ... */
constexpr auto gen_opcodes()
{
std::array<Opcode, 2> ret {};
ret[0] = { f<3> };
ret[1] = { f<2> };
return ret;
}
This fails:
constexpr auto gen_opcodes()
{
std::array<Opcode, 100> ret {};
for (int i = 30; i < 50; ++i)
{
ret[i] = { f<i> };
}
return ret;
}
NB: This is obviously for the sake of the example, it's slightly different in practice.
The reason is that i
in this context is not a constant expression.
How would one generate those function templates without writing them by hand? Otherwise is there a different solution that is "short enough" and has the advantages I was talking about earlier?
I had some ideas but none seems to be sufficient:
std::function
does not work in a constexpr
context because it has a non-trivial destructor and capturing lambdas can't cast into a function pointer.constexpr
context.Upvotes: 2
Views: 92
Reputation: 66200
The reason is that i in this context is not a constant expression
So the trick is transform i
in a constant expression.
You are using the std::array
's operator[]
in a constexpr
function, so you're using C++17.
With C++17 you can use std::index_sequence
/std::make_index_sequence
(available starting from C++14) and template folding (from C++17).
So you can write gen_opcodes()
in two steps.
A first step that generate a std::index_sequence
with the length of your loop
constexpr auto gen_opcodes_1()
{ return gen_opcodes_2(std::make_index_sequence<20U>{}); }
and a second step that uses template folding to simulate the for-loop
template <std::size_t ... Is>
constexpr auto gen_opcodes_2 (std::index_sequence<Is...> const &)
{
std::array<Opcode, 100> ret {};
( (ret[30+Is] = { f<30+Is> }), ... );
return ret;
}
The following is a full compiling example
#include <array>
struct CPU
{ int some_state = 0; };
template <int I>
void f (CPU & cpu)
{ cpu.some_state = I; }
struct Opcode
{
using Runner = void(*)(CPU&);
Runner run = [](CPU&) {};
};
template <std::size_t ... Is>
constexpr auto gen_opcodes_2 (std::index_sequence<Is...> const &)
{
std::array<Opcode, 100> ret {};
( (ret[30+Is] = { f<30+Is> }), ... );
return ret;
}
constexpr auto gen_opcodes_1()
{ return gen_opcodes_2(std::make_index_sequence<20U>{}); }
constexpr auto opcodes = gen_opcodes_1();
int main()
{
CPU cpu;
opcodes[1].run(cpu);
}
Upvotes: 2