初始化枚举索引数组?

11

在 C 语言中,gcc 提供了一个非常好的扩展,可以使用枚举作为数组的键,从而存储数据:

 enum keys
 {
      key_alpha = 0,
      key_beta = 1,
      key_gamma = 2
 };

 ValType values = 
 {
      [ key_alpha ] = { 0x03b1,"alpha" },
      [ key_gamma ] = { 0x03b3,"gamma" },
      [ key_beta ]  = { 0x03b2,"beta" }
 };

这很好,因为如果列表需要更改,添加或删除一行不会破坏赋值,很明显哪个键对应哪个值,并且产生的代码与普通标准数组初始化没有区别。

不幸的是,这种扩展在g++中不可用。

在C ++中,做同样的事情的首选轻量级方法是什么?最好不要使用基于字符串键、隐藏索引、重型模板或其他CPU和内存负荷重的类似<map>的东西。


3
C++11在解决方案中可行吗? - Flexo
2
它被称为“指定初始化程序”,在C99中可用,编译器可以提供此扩展功能。 - Nawaz
(顺便说一句,你的示例代码缺少分号) - Flexo
我们可以将问题重新表述为:“在C++中,是否可以通过指定任意顺序的元素来初始化数组?” - Kerrek SB
@Kerrek:不一定。GCC的旧版本有一个简化版,需要保持顺序和序列 - 它不会在编译时修改行为,密钥只用于检查正确性,如果声明的密钥与顺序不匹配,则打印错误。这将是令人满意的。例如,当前的排列会生成错误,并且要编译gamma,必须在beta之后进行。 - SF.
显示剩余2条评论
3个回答

15
#include <iostream>

#define KEYS_DEF \
    KEY_DEF( alpha, 0x03b1, "alpha" ),  \
    KEY_DEF( beta,  0x03b2, "beta" ),   \
    KEY_DEF( gamma, 0x03b3, "gamma" )

#define KEY_DEF( identifier, id, name )  identifier
enum keys { KEYS_DEF };

#undef KEY_DEF
#define KEY_DEF( identifier, id, name )  { id, name }
struct ValType { int id; char const* name; };
ValType const values[] = { KEYS_DEF };

int main()
{
    using namespace std;
    for( int i = alpha;  i <= gamma;  ++i )
    {
        cout << values[i].name << endl;
    }
}

4

我怀疑这个扩展的存在正是因为没有简单、便携的方法来实现这种行为。您可以使用类似以下的方法来模拟:

enum keys
{
  key_alpha = 0,
  key_beta = 1,
  key_gamma = 2
};

struct ValType {
  int v;
  const char *name;
};

template <int key>
struct param;

#define SETPARAM(key,value1,value2) \
template <> \
struct param< (key) > { \
  static constexpr ValType t {(value1),(value2)}; \
}

SETPARAM(key_alpha, 0x03b1,"alpha");
SETPARAM(key_gamma, 0x03b3,"gamma");
SETPARAM(key_beta, 0x03b2,"beta");

这是一个便携且符合您需求的模板,但它并不特别“笨重”。

如果您没有使用C++11也可以实现这一点,但是用于特化param模板的宏会稍微变长。


修改使像这样使用 int i = someinput(); cout << param<i>::t.name; 合法:

#include <cassert>

enum keys
{
  key_alpha = 0,
  key_beta = 1,
  key_gamma = 2
};

struct ValType {
  int v;
  const char *name;
};

template <int key>
struct param {
  enum { defined = false };
  static constexpr ValType t {0, 0};
};

template <int key>
constexpr ValType param<key>::t;

static const int MAXPARAM=255;

#define SETPARAM(key,value1,value2) \
template <> \
struct param< (key) > { \
  static_assert(key <= MAXPARAM, "key too big"); \
  enum { defined = true }; \
  static constexpr ValType t {(value1),(value2)}; \
}; \
constexpr ValType param<(key)>::t

template <int C=0>
struct get_helper {
  static const ValType& get(int i) {
    return i==0 ? (check(), param<C>::t) : get_helper<C+1>::get(i-1);
  }
private:
  static void check() {
    assert(param<C>::defined);
  }
};

template <>
struct get_helper<MAXPARAM> {
  static const ValType& get(int) {
    assert(false);
  }
};

const ValType& GETPARAM(int key) {
  return get_helper<>::get(key);
}

关键是实例化get_helper,并通过可以用于断言索引的标志递归调用。如果需要,您可以增加MAXPARAM,但这会使编译变慢。

示例用法仍然很简单:

#include "enumidx.hh"
#include <iostream>

SETPARAM(key_alpha, 0x03b1,"alpha");
SETPARAM(key_gamma, 0x03b3,"gamma");
SETPARAM(key_beta, 0x03b2,"beta");

int main() {
  int key = key_beta;
  const ValType& v = GETPARAM(key);
  std::cout << v.name << std::endl;
}

为了在任何给定的程序中拥有多个这样的结构体,您可以使用匿名命名空间和/或使基础结构体的名称(在此示例中为param)成为宏参数,并添加另一个宏STARTPARAM(?)来定义该名称的未特化模板。

这不允许动态查找,所以我认为它不是一个有效的替代品。 - Ben Voigt
@BenVoigt - 你可能是对的,我没有想到那个。你的意思是说例如 int i = someinput(); cout << param<i>::t.name; 是不合法的吗? - Flexo
@SF - 我可以修复这个问题,但这会将其推向“重型模板”的领域。我认为你不会得到一个不基本上只是std::map或“重型模板”的答案。 - Flexo
哦,我同意,但是为什么C++中没有呢? - SF.
@SF - 我怀疑在C++中它被视为低重要性,因为像std::map这样的容器是无处不在的。 - Flexo
显示剩余4条评论

2

一种廉价、隐秘、欺骗性的解决方案:在所有 .cpp 文件旁边的一个单独的 .c 文件中定义 "values" 变量,定义枚举和 "extern values" 在一个 .h 文件中。


Android init文件解析器中提供了一个很好的例子。头部中的值可以在此处找到,变量则在c 文件中。 - Jeremy Rocher

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接