我正在研究GameBoy模拟器。出于好奇,我想生成一个constexpr
Opcode
对象的数组,其中包含一个run()
函数指针以及其他一些有用的字段。
这是一个大致的工作示例:
#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);
}
将数组设为constexpr
的原因是我希望编译器能够对其进行优化。以我的理解,如果数组仅为const
,那么编译器很难优化掉run()
调用,因为这些调用是通过编译器应该内联它们的方式单独调用的,即{ {1}}。
但是,此方法的好处是立即生成一堆if (something == 0x00) { opcodes[0x00].run(blahblah); }
。我想到要使用模板函数,因为有些opcodes
会变成一种模式,所以我应该可以轻松地一次生成数十个opcodes
!
但是以下方法可行:
opcodes
此操作失败:
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;
}
注意:这显然是出于示例的目的,在实践中稍有不同。
原因是constexpr auto gen_opcodes()
{
std::array<Opcode, 100> ret {};
for (int i = 30; i < 50; ++i)
{
ret[i] = { f<i> };
}
return ret;
}
在此上下文中不是常量表达式。
如何不用手工编写即可生成这些功能模板?否则,是否存在另一种“足够短”的解决方案,并且具有我之前提到的优点?
我有一些想法,但似乎还不够:
i
在std::function
上下文中不起作用,因为它具有非平凡的析构函数,并且捕获的lambda无法转换为函数指针。constexpr
上下文中以这种方式存储它们。答案 0 :(得分:2)
原因是在这种情况下,我不是常数表达式
所以诀窍是将i
转换为常量表达式。
您在std::array
函数中使用了operator[]
的{{1}},因此您正在使用C ++ 17。
对于C ++ 17,您可以使用constexpr
/ std::index_sequence
(从C ++ 14开始可用)和模板折叠(从C ++ 17开始)。
因此您可以分两步编写std::make_index_sequence
。
第一步,它会根据循环的长度生成一个gen_opcodes()
std::index_sequence
第二步,使用模板折叠来模拟for循环
constexpr auto gen_opcodes_1()
{ return gen_opcodes_2(std::make_index_sequence<20U>{}); }
以下是完整的编译示例
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;
}