我正在开发 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() 调用,因为这些调用是以这种方式单独调用的编译器应该内联它们,即 if (something == 0x00) { opcodes[0x00].run(blahblah); }
.
但是,这种方法的好处是一次生成一堆操作码
。我想到使用模板函数,当一些操作码
进入一种模式时,我应该能够轻松地一次生成数十个操作码
!
但是虽然以下方法有效:
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
不是常量表达式。
如何在不手工编写的情况下生成这些函数模板?否则是否有一种“足够短”且具有我之前讨论的优点的不同解决方案?
我有一些想法,但似乎还不够:
- 有状态的 lambda。但是,
std::function
无法在constexpr
上下文中工作,因为它有一个不平凡的析构函数,并且捕获 lambda 无法转换为函数指针。 - 函数对象。我不知道如何将它们以这种方式存储在
constexpr
上下文中。
最佳答案
The reason is that i in this context is not a constant expression
所以技巧就是将 i
转换为常量表达式。
您正在 constexpr
函数中使用 std::array
的 operator[]
,因此您使用的是 C++ 17.
在 C++17 中,您可以使用 std::index_sequence
/std::make_index_sequence
(从 C++14 开始可用)和模板折叠(从 C+ +17)。
因此您可以分两步编写gen_opcodes()
。
第一步,生成带有循环长度的 std::index_sequence
constexpr auto gen_opcodes_1()
{ return gen_opcodes_2(std::make_index_sequence<20U>{}); }
第二步,使用模板折叠来模拟 for 循环
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;
}
以下是完整的编译示例
#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);
}
关于c++ - 在 constexpr 函数中实例化多个模板,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51347447/