在constexpr函数中插入许多模板

Instanciating many templates in constexpr function

本文关键字:许多模 插入 constexpr 函数      更新时间:2023-10-16

我正在开发GameBoy模拟器。出于好奇,我想生成一个constexprOpcode对象的数组,其中包含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); }

然而,这种方法的兴趣是同时生成一堆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在这个上下文中不是一个常量表达式。

如果不手工编写,如何生成这些函数模板否则,是否有另一种"足够短"的解决方案,并且具有我之前谈到的优势?

我有一些想法,但似乎都不够:

  • 有状态的lambdas。然而,std::functionconstexpr上下文中不起作用,因为它有一个非平凡的析构函数,并且捕获lambda不能转换为函数指针
  • 函数对象。我不知道如何在constexpr上下文中以这种方式存储它们

原因是i在这个上下文中不是一个常量表达式

所以诀窍是在常量表达式中转换i

您在constexpr函数中使用的是std::arrayoperator[],所以您使用的是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 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;
}

以下是的完整编译示例

#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);
}