c++ - 在 constexpr 函数中实例化多个模板

标签 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() 调用,因为这些调用是以这种方式单独调用的编译器应该内联它们,即 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::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 循环

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/

相关文章:

c++ - 如何使用 Crypto++ 解析 ZIP 文件?

c++ - Clang:从命令行或 Python 可靠地检测支持的 C++ 标准

C++ - 将变量和函数添加到基于模板参数的模板化类

c++ - 指向基类的指针的 std::vector 的深拷贝

c++ - 递归可变参数模板的基本案例特化

c++ - 如何有条件地实例化具有多个模板参数的模板类?

c# - 从 C# 访问 C++ 类枚举值

arrays - Twig 模板 : Add html in JOIN function

c++ - 在模板函数中包含不变假设

c++ - static_assert 一个类型属于 std::variant 接受的类型