在编译时使用Constexpr填充数组

时间:2012-11-09 18:38:41

标签: c++ templates c++11 constexpr

我想使用constexpr填充一个枚举数组。 数组的内容遵循某种模式。

我有一个枚举将ASCII字符集分为四类。

enum Type {
    Alphabet,
    Number,
    Symbol,
    Other,
};

constexpr Type table[128] = /* blah blah */;

我想要一个128 Type的数组。它们可以是一个结构。 数组的索引将对应于ASCII字符,值将是每个字符的Type

因此,我可以查询此数组以找出ASCII字符属于哪个类别。像

这样的东西
char c = RandomFunction();
if (table[c] == Alphabet) 
    DoSomething();

我想知道如果没有一些冗长的宏观黑客,这是否可行。

目前,我通过执行以下操作来初始化表格。

constexpr bool IsAlphabet (char c) {
    return ((c >= 0x41 && c <= 0x5A) ||
            (c >= 0x61 && c <= 0x7A));
}

constexpr bool IsNumber (char c) { /* blah blah */ }

constexpr bool IsSymbol (char c) { /* blah blah */ }

constexpr Type whichCategory (char c) { /* blah blah */ }

constexpr Type table[128] = { INITIALIZE };

其中INITIALIZE是一些非常冗长的宏黑客攻击的入口点。 像

这样的东西
#define INITIALIZE INIT(0)
#define INIT(N) INIT_##N
#define INIT_0 whichCategory(0), INIT_1
#define INIT_1 whichCategory(1), INIT_2
//...
#define INIT_127 whichCategory(127)

我想要一种方法来填充这个数组或包含该数组的结构,而不需要这个宏hack ......

也许像

struct Table {
    Type _[128];
};

constexpr Table table = MagicFunction();

所以,问题是如何写这个MagicFunction

注意:我知道cctype和喜欢,这个问题更像是Is this possible?而不是Is this the best way to do it?

任何帮助都将不胜感激。

谢谢,

4 个答案:

答案 0 :(得分:26)

忽略所有问题,indices救援:

template<unsigned... Is> struct seq{};
template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};
template<unsigned... Is>
struct gen_seq<0, Is...> : seq<Is...>{};

template<unsigned... Is>
constexpr Table MagicFunction(seq<Is...>){
  return {{ whichCategory(Is)... }};
}

constexpr Table MagicFunction(){
  return MagicFunction(gen_seq<128>{});
}

Live example.

答案 1 :(得分:8)

在C ++中,17 ::std::array已被更新为constexpr友好,你可以像在C ++ 14中那样做,但没有一些可怕的黑客攻击在关键的地方缺乏constexpr。这是代码的样子:

#include <array>

enum Type {
    Alphabet,
    Number,
    Symbol,
    Other,
};

constexpr ::std::array<Type, 128> MagicFunction()
{
   using result_t = ::std::array<Type, 128>;
   result_t result = {Other};
   result[65] = Alphabet;
   //....
   return result;
}

const ::std::array<Type, 128> table = MagicFunction();

再次MagicFunction仍然需要遵守相当宽松的constexpr规则。主要是,它可能不会修改任何全局变量或使用new(这意味着修改全局状态,即堆)或其他类似的东西。

答案 2 :(得分:4)

恕我直言,最好的方法是编写一个小的安装程序,为您生成table。然后你可以抛弃安装程序,或者与生成的源代码一起检查它。

这个问题的棘手部分只是另一个问题的重复:Is it possible to create and initialize an array of values using template metaprogramming?

诀窍是,写

之类的东西是不可能的
Type table[256] = some_expression();

在文件范围内,因为全局数组只能使用文字(源级)初始化列表进行初始化。您无法使用constexpr函数的结果初始化全局数组,即使您可以某种方式使该函数返回std::initializer_list,但由于其构造函数未被声明,因此无法返回constexpr { {1}}。

所以你需要做的是让编译器为你生成数组,方法是使它成为模板类的static const数据成员。经过一两个级别的元编程后,我太难以写出来了,你会在一行看起来像是

template <int... Indices>
Type DummyStruct<Indices...>::table[] = { whichCategory(Indices)... };

其中Indices是一个看起来像0,1,2,... 254,255的参数包。您可以使用递归帮助程序模板构造该参数包,也可以仅使用Boost中的某些内容。然后你可以写

constexpr Type (&table)[] = IndexHelperTemplate<256>::table;

...但是为什么你会这样做,当表只有256个条目,除非ASCII本身改变,否则永远不会改变?正确的方式最简单的方法:预先计算所有256个条目并明确地写出表格,没有模板,constexpr或任何其他魔法。

答案 3 :(得分:4)

在C ++ 14中执行此操作的方法如下所示:

#include <array>

enum Type {
    Alphabet,
    Number,
    Symbol,
    Other,
};

constexpr ::std::array<Type, 128> MagicFunction()
{
   using result_t = ::std::array<Type, 128>;
   result_t result = {Other};
   const result_t &fake_const_result = result;
   const_cast<result_t::reference>(fake_const_result[65]) = Alphabet;
   //....
   return result;
}

const ::std::array<Type, 128> table = MagicFunction();

不再需要聪明的模板hackery。但是,因为C ++ 14并没有真正对标准库中的constexpr进行彻底的审查,所以必须使用涉及const_cast的可怕黑客。

当然,MagicFunction最好不要修改任何全局变量或以其他方式违反constexpr规则。但是现在这些规则相当自由。例如,您可以修改所需的所有局部变量,但通过引用传递它们或获取它们的地址可能不会很好。

请参阅我对C ++ 17的另一个答案,它允许你放弃一些丑陋的黑客。