(C++)编译时自动生成switch语句case

在我的程序中,我有一些看起来像这样的代码:一组函数或类,它们通过模板特化独特地实现了一个通用模板:

constexpr int NUM_SPECIALIZATIONS = 32;

template<int num>
void print_num(){}

template<>
void print_num<0>(){cout << "Zero" << endl;}

template<>
void print_num<1>(){cout << "One" << endl;}

template<>
void print_num<2>(){cout << "Two" << endl;}

// etc, ...

template<>
void print_num<31>(){cout << "Thirty-One" << endl;}

然后我有一个变量,它的值只在运行时才知道:

int my_num;
cin >> my_num; // Could be 0, could be 2, could be 27, who tf knows

然后我需要调用对应于变量值的模板特化。由于我不能使用变量作为模板参数,我会创建一种“解释器”:

switch(my_num)
{
  case 0:
  print_num<0>();
  break;
  case 1:
  print_num<1>();
  break;
  case 2:
  print_num<2>();
  break;
  // etc, ...
  case 31:
  print_num<31>();
  break;
}

我注意到这段代码的第一件事是它是重复的。当然,必须有某种技巧来程序生成此代码。

我注意到的另一件事是维护不方便,因为它与模板特化相结合;每次我想添加一个新的模板专业化时,我也需要更新解释器。

理想情况下,我将能够使用某种模板魔术在编译时自动生成解释器,以便在保持 switch 语句的效率的同时,代码的两个部分可以保持解耦:

// Copies and pastes the code found in template lambda "foo",
// Replacing all occurrences of its template parameter with values from
// "begin" until "end"
template<auto begin, auto end>
inline void unroll(auto foo)
{
  if constexpr(begin < end)
  {
    foo.template operator()<begin>();
    unroll<begin + 1, end>(foo);
  }
}

// A template lambda which generates a generic switch case for the interpreter
auto template_lambda = [&]<int NUM>()
{
  case NUM:
  print_num<NUM>();
  break;
};

// The interpreter; contains the code "case NUM: print_num<NUM>(); break;"
// repeated for all ints NUM such that 0 <= NUM < NUM_SPECIALIZATIONS
switch(my_num)
{
  unroll<0,NUM_SPECIALIZATIONS>(template_lambda);
}

不幸的是,这段代码不能编译。它永远不会通过语法检查器,因为从技术上讲,我的 lambda 函数中的“case”和“break”语句不在 switch 语句中。

为了使其工作,我需要使用宏而不是模板和 lambda 来实现“展开”功能,以便源代码的复制和粘贴发生语法检查之前而不是之后。


我玩过的另一种解决方案是模仿 switch 语句在低级别上的作用。我可以创建一个函数指针数组作为跳转表:

std::array<std::function<void()>,NUM_SPECIALIZATIONS> jump_table;

然后我可以用指向各种模板特化的指针来填充跳转表,而不必将它们全部键入。相反,我可以使用 unroll 函数:

template<auto begin, auto end>
inline void unroll(auto foo)
{
  if constexpr(begin < end)
  {
    foo.template operator()<begin>();
    unroll<begin + 1, end>(foo);
  }
}

unroll<0,NUM_SPECIALIZATIONS>([&]<int NUM>()
{
  jump_table[NUM] = print_num<NUM>;
});

我们走了。现在解释器和模板特化是分离的。

然后,当我想将相应的模板特化调用到运行时变量的值时my_num,我可以这样做:

jump_table[my_num](); // Almost like saying print_num<my_num>();

甚至可以在运行时修改跳转表,只需将数组的内容重新分配给不同的函数名称:

jump_table[NUM] = /* a different function name */;

这种方法的缺点是,与 switch 语句相比,仅通过访问数组元素就会产生轻微的运行时损失。我认为这是固有的,因为 switch 语句在编译时在指令内存中生成它们的跳转表,而我在这里所做的是在运行时在数据内存中生成跳转表。

我认为只要函数具有足够长的执行时间,轻微的运行时损失就无关紧要,相比之下,此时的开销可以忽略不计。

回答

你可以这样做:

namespace detail {
    template<size_t I>
    void run(size_t idx) {
        if (I == idx) {
            print_num<I>();
        } 
        else if constexpr (I+1 < NUM_SPECIALIZATIONS) {
            run<I+1>(idx);
        }
    }
}

void run(size_t idx) {
    detail::run<0>(idx);
}

然后像这样调用它:

int my_num;
cin >> my_num;
if (my_num >= 0)
   run(my_num);

然而,由于可能存在深度递归,编译时间可能会受到影响,具体取决于专业化的数量。


以上是(C++)编译时自动生成switch语句case的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>