在编译时使用Constexpr填充数组

时间:2021-08-24 22:56:56

I would like to populate an array of enum using constexpr. The content of the array follows a certain pattern.

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

I have an enum separating ASCII character set into four categories.

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

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

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

I would like to have an array of 128 Type. They can be in a structure. The index of the array will be corresponding to the ASCII characters and the value will be the Type of each character.

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

So I can query this array to find out which category an ASCII character belongs to. Something like

所以我可以查询这个数组,找出ASCII字符属于哪个类别。就像是

char c = RandomFunction();
if (table[c] == Alphabet) 
    DoSomething();

I would like to know if this is possible without some lengthy macro hacks.

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

Currently, I initialize the table by doing the following.

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

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

where INITIALIZE is the entry point of some very lengthy macro hacks. Something like

其中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)

I would like a way to populate this array or a structure containing the array without the need for this macro hack...

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

Maybe something like

也许是这样的

struct Table {
    Type _[128];
};

constexpr Table table = MagicFunction();

So, the question is how to write this MagicFunction?

那么,问题是如何编写这个MagicFunction?

Note: I am aware of cctype and likes, this question is more of a Is this possible? rather than Is this the best way to do it?.

注意:我知道cctype和喜欢,这个问题更多的是这可能吗?而不是这是最好的方式吗?

Any help would be appreciated.

任何帮助,将不胜感激。

Thanks,

4 个解决方案

#1


26  

Ignoring ALL the issues, indices to the rescue:

无视所有问题,救援指数:

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.

#2


7  

In C++17 ::std::array has been updated to be more constexpr friendly and you can do the same as in C++14, but without some of the scary looking hacks to get around the lack of constexpr in crucial places. Here is what the code would look like there:

在C ++ 17中,:: std :: array已被更新为更加友好的,你可以像在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();

Again MagicFunction still needs to obey the rather loose constexpr rules. Mainly, it may not modify any global variables or use new (which implies modifying global state, namely the heap) or other such things.

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

#3


4  

IMHO the best way to do this is simply write a tiny setup program that will generate table for you. And then you can either throw out the setup program, or check it in alongside the generated source code.

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

The tricky part of this question is just a duplicate of this other one: Is it possible to create and initialize an array of values using template metaprogramming?

这个问题的棘手部分只是另一个问题的重复:是否可以使用模板元编程创建和初始化值数组?

The trick is, it's impossible to write anything like

诀窍是,写不出任何东西是不可能的

Type table[256] = some_expression();

at file scope, because global arrays can be initialized only with literal (source-level) initializer-lists. You can't initialize a global array with the result of a constexpr function, even if you could somehow get that function to return a std::initializer_list, which you can't because its constructor isn't declared constexpr.

在文件范围内,因为全局数组只能使用文字(源级)初始化列表进行初始化。你不能用constexpr函数的结果初始化一个全局数组,即使你能以某种方式获得该函数来返回一个std :: initializer_list,你不能这样做,因为它的构造函数没有被声明为constexpr。

So what you have to do is get the compiler to generate the array for you, by making it a static const data member of a template class. After one or two levels of metaprogramming that I'm too confused to write out, you'll bottom out in a line that looks something like

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

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

where Indices is a parameter-pack that looks like 0,1,2,... 254,255. You construct that parameter-pack using a recursive helper template, or maybe just using something out of Boost. And then you can write

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

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

...But why would you do all that, when the table is only 256 entries that will never change unless ASCII itself changes? The right way is the simplest way: precompute all 256 entries and write out the table explicitly, with no templates, constexpr, or any other magic.

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

#4


3  

The way to do this in C++14 looks like this:

在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();

No clever template hackery required any longer. Though, because C++14 didn't really undergo a thorough enough review of what did and didn't have to be constexpr in the standard library, a horrible hack involving const_cast has to be used.

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

And, of course, MagicFunction had better not modify any global variables or otherwise violate the constexpr rules. But those rules are pretty liberal nowadays. You can, for example, modify all the local variables you want, though passing them by reference or taking their addresses may not work out so well.

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

See my other answer for C++17, which allows you to drop some of the ugly-looking hacks.

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

#1


26  

Ignoring ALL the issues, indices to the rescue:

无视所有问题,救援指数:

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.

#2


7  

In C++17 ::std::array has been updated to be more constexpr friendly and you can do the same as in C++14, but without some of the scary looking hacks to get around the lack of constexpr in crucial places. Here is what the code would look like there:

在C ++ 17中,:: std :: array已被更新为更加友好的,你可以像在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();

Again MagicFunction still needs to obey the rather loose constexpr rules. Mainly, it may not modify any global variables or use new (which implies modifying global state, namely the heap) or other such things.

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

#3


4  

IMHO the best way to do this is simply write a tiny setup program that will generate table for you. And then you can either throw out the setup program, or check it in alongside the generated source code.

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

The tricky part of this question is just a duplicate of this other one: Is it possible to create and initialize an array of values using template metaprogramming?

这个问题的棘手部分只是另一个问题的重复:是否可以使用模板元编程创建和初始化值数组?

The trick is, it's impossible to write anything like

诀窍是,写不出任何东西是不可能的

Type table[256] = some_expression();

at file scope, because global arrays can be initialized only with literal (source-level) initializer-lists. You can't initialize a global array with the result of a constexpr function, even if you could somehow get that function to return a std::initializer_list, which you can't because its constructor isn't declared constexpr.

在文件范围内,因为全局数组只能使用文字(源级)初始化列表进行初始化。你不能用constexpr函数的结果初始化一个全局数组,即使你能以某种方式获得该函数来返回一个std :: initializer_list,你不能这样做,因为它的构造函数没有被声明为constexpr。

So what you have to do is get the compiler to generate the array for you, by making it a static const data member of a template class. After one or two levels of metaprogramming that I'm too confused to write out, you'll bottom out in a line that looks something like

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

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

where Indices is a parameter-pack that looks like 0,1,2,... 254,255. You construct that parameter-pack using a recursive helper template, or maybe just using something out of Boost. And then you can write

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

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

...But why would you do all that, when the table is only 256 entries that will never change unless ASCII itself changes? The right way is the simplest way: precompute all 256 entries and write out the table explicitly, with no templates, constexpr, or any other magic.

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

#4


3  

The way to do this in C++14 looks like this:

在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();

No clever template hackery required any longer. Though, because C++14 didn't really undergo a thorough enough review of what did and didn't have to be constexpr in the standard library, a horrible hack involving const_cast has to be used.

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

And, of course, MagicFunction had better not modify any global variables or otherwise violate the constexpr rules. But those rules are pretty liberal nowadays. You can, for example, modify all the local variables you want, though passing them by reference or taking their addresses may not work out so well.

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

See my other answer for C++17, which allows you to drop some of the ugly-looking hacks.

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