【问题标题】:Populate An Array Using Constexpr at Compile-time在编译时使用 Constexpr 填充数组
【发布时间】:2012-11-09 18:38:40
【问题描述】:

我想使用 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();

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

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

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 是一些非常冗长的宏 hack 的入口点。 类似的东西

#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和likes,这个问题更像是Is this possible?而不是Is this the best way to do it?

任何帮助将不胜感激。

谢谢,

【问题讨论】:

  • 你知道 ASCII 只在[0 .. 127] 范围内吗? char 的签名是实现定义的吗?你目前的做法是非常危险的。哦,最后但同样重要的是,C++ 标准根本不需要 ASCII 编码。也可能是 EBCDIC。
  • 好消息是,因为数组可以用包扩展来初始化,所以你要求的确实是可行的。您只需要多次调用该函数:p
  • @DyP: ((c &gt;= 0x41 &amp;&amp; c &lt;= 0x5A) || (c &gt;= 0x61 &amp;&amp; c &lt;= 0x7A)) from IsAlphabet -- 这假定了 ASCII 中存在的十进制顺序。签名很重要,因为 OP 传递文字 &gt; 127,它可能映射到负数 chars。
  • @BeyondSora:对不起,我昨天没有时间正确解决这个问题。谢天谢地,Xeo 做到了(尽管他脾气暴躁:p)。索引生成与包扩展相结合是生成各种初始化列表的好方法(如您在此处看到的),因此我指出了这样一个事实,即由于数组接受初始化列表,所以您很出色。
  • @DyP:使用 EBCDIC,0x41 根本不会映射到任何符号,并且字母在代码页中的位置很奇怪。看看here

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


【解决方案1】:

忽略所有问题,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.

【讨论】:

  • @Steven:添加了 Lounge wiki 条目的链接。它基本上构建了一个列表[0 .. 127] 并对其进行扩展,调用whichCategory(0), whichCategory(1), ..., whichCategory(127) 并将其作为初始化参数传递给Table._(注意用于初始化内部数组的双{})。
  • 我不知道你可以返回这种形式的东西 { /*...*/ },这是 C++11 中的新东西还是标准中一直存在的东西?
  • @BeyondSora:C++11 的新成员,称为 list-initialization(也不太正式,通常称为统一初始化)。
  • 为什么要用双花括号?
  • 此答案在 C++14 中已过时,因为 C++14 允许您在 constexpr 函数中构建整个数组。
【解决方案2】:

在 C++17 中,::std::array 已更新为对 constexpr 更友好,您可以像在 C++14 中那样做,但没有一些看起来很吓人的 hack 来解决缺少 @987654323 的问题@在关键的地方。下面是代码的样子:

#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(表示修改全局状态,即堆)之类的东西。

【讨论】:

  • 我来到这里试图用result[65] = std::make_pair( x,y ) 填充这样的std::array&lt;std::pair&lt;X,Y&gt;&gt;。这不起作用,但result[65].first = x; result[65].second = y; 会。
【解决方案3】:

恕我直言,最好的方法是编写一个小型安装程序,它将为您生成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

因此,您要做的就是让编译器为您生成数组,方法是使其成为模板类的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 或任何其他魔法。

【讨论】:

  • 恕我直言,硬编码很糟糕,因为如果这很复杂(在更通用的情况下),了解该值的逻辑很重要。如果有 n 个预先生成的阶乘值来确保我的某些值没有复制粘贴错误或错误?
  • 如果有 n 行复杂的元编程来计算值,谁来确保该代码中没有复制粘贴错误或错误?我们的目标始终是减少出错的可能性。 对于像三行factorial 函数这样的东西,也许你会通过编写代码而不是数据来最小化出错的可能性。对于 OP 的实际问题——一个 128 字节的分类表——我仍然强烈地认为,编写数据而不是代码是最大程度地减少出错可能性的最佳方法。 (但请注意,自 2012 年以来,constexpr 编程变得更加强大和自然。)
  • 比起硬编码的数字,我更喜欢查看数据背后的内容。
【解决方案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();

不再需要聪明的模板黑客。不过,由于 C++14 并没有真正对标准库中的 constexpr 进行足够彻底的审查,因此必须使用涉及 const_cast 的可怕 hack。

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

请参阅我对 C++17 的其他答案,它可以让您放弃一些看起来难看的 hack。

【讨论】:

  • 您甚至可以在MagicFunction 中使用for 循环。
  • 我收到一个关于 result 被声明为未初始化的错误。 std::array&lt;Type, 128&gt; result{}; 确实有效 - 我猜 Type{}Alphabet
  • @user1754322 - 看到这个问题:stackoverflow.com/questions/1661912/…
  • Ummm... 这不能用 C++14 编译。您需要一个仅存在于 C++17 中的 constexpr reference operator[]( size_type pos );en.cppreference.com/w/cpp/container/array/operator_at
  • @TimRae - 完成。 :-)
猜你喜欢
  • 2023-03-23
  • 1970-01-01
  • 2019-01-16
  • 2015-07-20
  • 2017-03-22
  • 1970-01-01
  • 2013-03-07
  • 1970-01-01
  • 2015-10-09
相关资源
最近更新 更多