在constexpr函数中实例化许多模板

时间:2018-07-15 10:23:49

标签: c++ templates c++17 template-meta-programming constexpr

我正在研究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; } 在此上下文中不是常量表达式。

如何不用手工编写即可生成这些功能模板?否则,是否存在另一种“足够短”的解决方案,并且具有我之前提到的优点?

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

  • 有状态的lambda。但是,istd::function上下文中不起作用,因为它具有非平凡的析构函数,并且捕获的lambda无法转换为函数指针。
  • 功能对象。我不知道如何在constexpr上下文中以这种方式存储它们。

1 个答案:

答案 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;
 }