迭代非增量枚举

39
在你提问前,我已经查看了搜索了相关的SO问题,但没有找到令人满意的答案。我需要能够动态遍历一个枚举类型,其中包含非增量值,例如:
typedef enum {
    CAPI_SUBTYPE_NULL = 0,               /* Null subtype. */
    CAPI_SUBTYPE_DIAG_DFD = 1,           /* Data Flow diag. */
    CAPI_SUBTYPE_DIAG_ERD = 2,           /* Entity-Relationship diag. */
    CAPI_SUBTYPE_DIAG_STD = 3,           /* State Transition diag. */
    CAPI_SUBTYPE_DIAG_STC = 4,           /* Structure Chart diag. */
    CAPI_SUBTYPE_DIAG_DSD = 5,           /* Data Structure diag. */
    CAPI_SUBTYPE_SPEC_PROCESS = 6,       /* Process spec. */
    CAPI_SUBTYPE_SPEC_MODULE = 7,        /* Module spec. */
    CAPI_SUBTYPE_SPEC_TERMINATOR = 8,    /* Terminator spec. */

    CAPI_SUBTYPE_DD_ALL = 13,            /* DD Entries (All). */
    CAPI_SUBTYPE_DD_COUPLE = 14,         /* DD Entries (Couples). */
    CAPI_SUBTYPE_DD_DATA_AREA = 15,      /* DD Entries (Data Areas). */
    CAPI_SUBTYPE_DD_DATA_OBJECT = 16,    /* DD Entries (Data Objects). */
    CAPI_SUBTYPE_DD_FLOW = 17,           /* DD Entries (Flows). */
    CAPI_SUBTYPE_DD_RELATIONSHIP = 18,   /* DD Entries (Relationships). */
    CAPI_SUBTYPE_DD_STORE = 19,          /* DD Entries (Stores). */

    CAPI_SUBTYPE_DIAG_PAD = 35,          /* Physical architecture diagram. */
    CAPI_SUBTYPE_DIAG_BD  = 36,          /* Behaviour diagram. */
    CAPI_SUBTYPE_DIAG_UCD = 37,          /* UML Use case diagram. */
    CAPI_SUBTYPE_DIAG_PD  = 38,          /* UML Package diagram. */
    CAPI_SUBTYPE_DIAG_COD = 39,          /* UML Collaboration diagram. */
    CAPI_SUBTYPE_DIAG_SQD = 40,          /* UML Sequence diagram. */
    CAPI_SUBTYPE_DIAG_CD  = 41,          /* UML Class diagram. */
    CAPI_SUBTYPE_DIAG_SCD = 42,          /* UML State chart. */
    CAPI_SUBTYPE_DIAG_ACD = 43,          /* UML Activity chart. */
    CAPI_SUBTYPE_DIAG_CPD = 44,          /* UML Component diagram. */
    CAPI_SUBTYPE_DIAG_DPD = 45,          /* UML Deployment diagram. */
    CAPI_SUBTYPE_DIAG_PFD = 47,          /* Process flow diagram. */
    CAPI_SUBTYPE_DIAG_HIER = 48,         /* Hierarchy diagram. */
    CAPI_SUBTYPE_DIAG_IDEF0 = 49,        /* IDEF0 diagram. */
    CAPI_SUBTYPE_DIAG_AID = 50,          /* AID diagram. */
    CAPI_SUBTYPE_DIAG_SAD = 51,          /* SAD diagram. */
    CAPI_SUBTYPE_DIAG_ASG = 59           /* ASG diagram. */
} CAPI_SUBTYPE_E ;

我希望能够做到这一点的原因是,枚举类型是在API中给定的(显然我无法更改),而我希望能够无论API版本如何都能够遍历这些值。

任何指导都将不胜感激。


4
C++没有直接支持枚举类型迭代的功能。可以将它们存储在std::vectorstd::array中来解决该问题。 - Captain Obvlious
我讨厌库像这样使用枚举。向量的建议很可靠。 - MobA11y
1
除了我之前的评论(感谢iammilind在另一个方向上的鼓励),我建议使用initializer_list而不是vectorarray,因为它保证是字面值 - Captain Obvlious
2
请参考以下链接中的最佳答案,其中介绍了如何为枚举类实现运算符:https://dev59.com/AWUp5IYBdhLWcg3wLlVV - SomeWittyUsername
15个回答

17

使用C++,枚举类型的迭代只能将其存储在数组中并遍历该数组。主要难点是如何跟踪enum声明和数组声明中的相同顺序?
您可以自动化方式来对enum和数组进行排序。我认为这是一种不错的方法:

// CAPI_SUBTYPE_E_list.h
// This header file contains all the enum in the order
// Whatever order is set will be followed everywhere
NAME_VALUE(CAPI_SUBTYPE_NULL, 0),         /* Null subtype. */
NAME_VALUE(CAPI_SUBTYPE_DIAG_DFD, 1),     /* Data Flow diag. */
NAME_VALUE(CAPI_SUBTYPE_DIAG_ERD, 2),     /* Entity-Relationship diag. */
...
NAME_VALUE(CAPI_SUBTYPE_DD_ALL, 13),      /* DD Entries (All). */
NAME_VALUE(CAPI_SUBTYPE_DD_COUPLE, 14),   /* DD Entries (Couples). */
...
NAME_VALUE(CAPI_SUBTYPE_DIAG_ASG, 59)     /* ASG diagram. */

现在你需要在枚举声明和数组声明两个地方都使用宏重定义,并且在其中包含这个文件:#include

// Enum.h
typedef enum {
#define NAME_VALUE(NAME,VALUE) NAME = VALUE
#include"CAPI_SUBTYPE_E_list.h"
#undef NAME_VALUE
}CAPI_SUBTYPE_E;

将同一个文件放在数组中和其他宏定义一起:

// array file
// Either this array can be declared `static` or inside unnamed `namespace` to make 
// ... it visible through a header file; Or it should be declared `extern` and keep ...
// ...  the record of its size; declare a getter method for both array and the size
unsigned int CAPI_SUBTYPE_E_Array [] = {
#define NAME_VALUE(NAME,VALUE) NAME
#include"CAPI_SUBTYPE_E_list.h"
#undef NAME_VALUE
};

现在在C++03中进行迭代:

for(unsigned int i = 0, size = sizeof(CAPI_SUBTYPE_E_Array)/sizeof(CAPI_SUBTYPE_E_Array[0]);
    i < size; ++i)

或者在C++11中更简单:

for(auto i : CAPI_SUBTYPE_E_Array)

1
不错的解决方案,唯一的缺点是每个枚举都需要一个头文件。 - Étienne
我没有得到答案,头文件如何在没有编译错误的情况下被定义?例如,CAPI_SUBTYPE_DD_ALL将未定义,以逗号结尾的头文件是否被接受?我们不会得到编译器错误吗?标识符未定义错误消息。 - dochoex
@dochoex,将头文件视为#define宏替换。无论“file.h”中的内容是什么,在找到#include<file.h>的地方都会被简单地替换。例如,在第一个案例中,CAPI_SUBTYPE_DD_ALL将被替换为enum CAPI_SUBTYPE_E中包含的CAPI_SUBTYPE_DD_ALL = 13 - iammilind

12

这是关于比C++更棘手且更需要C练习的问题,但你可以使用X宏。这非常丑陋,需要保持TABLE的正确顺序。在C++中,我认为我们不需要遍历枚举,而且我们也不需要为枚举赋值(表面上,每次编译枚举值都是随机的)。所以把它当作一个笑话 :)

#include <iostream>

#define CAPI_SUBTYPE_TABLE \
    CAPI_SUBTYPE_X(CAPI_SUBTYPE_NULL,     0 ) \
    CAPI_SUBTYPE_X(CAPI_SUBTYPE_DIAG_DFD, 1 ) \
    CAPI_SUBTYPE_X(CAPI_SUBTYPE_DD_ALL,   13)

#define CAPI_SUBTYPE_X(name, value) name = value,
enum CAPI_SUBTYPE
{
    CAPI_SUBTYPE_TABLE
    CAPI_SUBTYPE_END
};
#undef CAPI_SUBTYPE_X

#define CAPI_SUBTYPE_X(name, value) name,
CAPI_SUBTYPE subtype_iteratable[] =
{
    CAPI_SUBTYPE_TABLE
    CAPI_SUBTYPE_END
};
#undef CAPI_SUBTYPE_X

#define CAPI_SUBTYPE_SIZE  (sizeof(subtype_iteratable) / sizeof(subtype_iteratable[0]) - 1)


int main()
{
    for (unsigned i = 0; i < CAPI_SUBTYPE_SIZE; ++i)
        std::cout << subtype_iteratable[i] << std::endl; // 0, 1, 13
}

3
我很好奇你所说的“表面上枚举值在每次编译时都是随机的”是什么意思,因为标准规定除非指定了一个值,否则默认值将分配给枚举类型的第一个值,该值为零。请参阅http://www.cprogramming.com/tutorial/enum.html和http://www.learncpp.com/cpp-tutorial/45-enumerated-types/。 - Richard Chambers
4
@RichardChambers,是的,你说得对。我不喜欢枚举被用作索引(例如遍历数组)。默认值会促使我们这样写代码:for(int i = 0; ENUM_FIRST; i < ENUM_LAST; ++i) do_smth(data[i])。而且,我也不喜欢由程序员分配枚举的值,因为标准没有防止我们分配重复的值。所以,“值是随机的”实际上意味着“尽管标准保证了枚举的默认值,但我更倾向于将枚举值视为随机的”。 - fasked
@fasked 当你需要解析一个你无法控制的标准协议发送的字节流时,手动定义值的枚举实际上是必需的。但无论如何还是加一分。 - Étienne

8
我同意已经给出的观点,即在不修改或复制enum的定义的情况下不可能实现。然而,在C++11(甚至是C++03?)中,您可以提供一种语法,只需从enum中复制和粘贴枚举器定义即可。 只要每个枚举器都有明确的定义(使用=),就可以使用此方法。
编辑:您甚至可以将其扩展到即使没有每个枚举器都有明确的定义,也可以使其工作,但在此情况下不应该需要这样做。
我曾经为一些物理学家开发过这个示例,所以示例是关于粒子的。

用法示例:

// required for this example
#include <iostream>

enum ParticleEnum
{
    PROTON = 11,
    ELECTRON = 42,
    MUON = 43
};

// define macro (see below)

MAKE_ENUM(
    ParticleEnum,                     // name of enum type
    particle_enum_detail,             // some namespace to place some types in
    all_particles,                    // name of array to list all enumerators

    // paste the enumerator definitions of your enum here
    PROTON = 11,
    ELECTRON = 42,
    MUON = 43
) // don't forget the macro's closing paranthesis

int main()
{
    for(ParticleEnum p : all_particles)
    {
        std::cout << p << ", ";
    }
}

该宏实际上会产生以下结果:
namespace particle_enum_detail
{
    // definition of a type and some constants

    constexpr ParticleEnum all_particles[] = {
        PROTON,
        ELECTRON,
        MUON
    };
}
using particle_enum_detail::all_particles;

宏定义

#define MAKE_ENUM(ENUM_TYPE, NAMESPACE, ARRAY_NAME, ...)                 \
    namespace NAMESPACE                                                  \
    {                                                                    \
        struct iterable_enum_                                            \
        {                                                                \
            using storage_type = ENUM_TYPE;                              \
            template < typename T >                                      \
            constexpr iterable_enum_(T p)                                \
                : m{ static_cast<storage_type>(p) }                      \
            {}                                                           \
            constexpr operator storage_type()                            \
            {  return m;  }                                              \
            template < typename T >                                      \
            constexpr iterable_enum_ operator= (T p)                     \
            {  return { static_cast<storage_type>(p) };  }               \
        private:                                                         \
            storage_type m;                                              \
        };                                                               \
                                                                         \
        /* the "enumeration" */                                          \
        constexpr iterable_enum_ __VA_ARGS__;                            \
        /* the array to store all "enumerators" */                       \
        constexpr ENUM_TYPE ARRAY_NAME[] = { __VA_ARGS__ };              \
    }                                                                    \
    using NAMESPACE::ARRAY_NAME;                              // macro end

注意:类型iterable_enum_也可以在宏外部定义一次。

宏解释

这个想法是允许在宏调用中使用proton = 11, electron = 12这样的语法。对于任何声明来说,这很容易实现,但对于存储名称来说却有问题:

#define MAKE_ENUM(ASSIGNMEN1, ASSIGNMENT2) \
    enum my_enum { ASSIGNMENT1, ASSIGNMENT2 }; \
    my_enum all[] = { ASSIGNMENT1, ASSIGNMENT2 };
MAKE_ENUM(proton = 11, electron = 22);

yields to:

enum my_enum { proton = 11, electron = 22 };    // would be OK
my_enum all[] = { proton = 11, electron = 22 }; // cannot assign to enumerator

与许多语法技巧一样,运算符重载提供了一种克服这个问题的方法;但是赋值运算符必须是成员函数-而枚举不是类。那么为什么不使用一些常量对象代替枚举呢?
enum my_enum { proton = 11, electron = 22 };
// alternatively
constexpr int proton = 11, electron = 12;
// the `constexpr` here is equivalent to a `const`

这还没有解决我们的问题,只是证明如果我们不需要枚举器的自动递增功能,我们可以轻松地用常量列表替换枚举。

现在,关于运算符重载的语法技巧:

struct iterable_enum_
{
    // the trick: a constexpr assignment operator
    constexpr iterable_enum_ operator= (int p)             // (op)
    {  return {p};  }

    // we need a ctor for the syntax `object = init`
    constexpr iterable_enum_(int p)                        // (ctor)
        : m{ static_cast<ParticleEnum>(p) }
    {}
private:
    ParticleEnum m;
};

constexpr iterable_enum_ proton = 11, electron = 22;              // (1)
iterable_enum_ all_particles[] = { proton = 11, electron = 22 };  // (2)

这个技巧在于,第一行中的=表示的是复制初始化,它通过将数字(1122)转换为particle类型的临时对象(使用(ctor)),再通过隐式定义的构造函数将该临时对象复制/移动到目标对象(protonelectron)。

相比之下,第二行中的=被解析为调用操作符(op),实际上返回了调用它的对象的副本(*this)。constexpr的限制允许在编译时使用这些变量,例如在模板声明中。由于constexpr函数的限制,我们不能简单地在(op)函数中返回*this。此外,constexpr意味着所有const的限制。

通过提供一个隐式转换运算符,您可以创建第二行中类型为ParticleEnum的数组:

// in struct particle
constexpr operator ParticleEnum() { return m; }

// in namespace particle_enum_detail
ParticleEnum all_particles[] = { proton = 11, electron = 22 };

其实我之前的回答是在讨论C++11,但后来我意识到它并没有比当前的C++03设施更有优势。目前我已经改用宏技巧来跟踪枚举/数组。看起来我们两个的回答都试图达到同样的目的。 - iammilind
@iammilind 我同意,我也读过你之前的版本。虽然我认为在我的答案中使用operator=技巧并不需要C++11(尽管可变宏不在C++03中)。operator=constexpr的,因为我之前使用非类型模板参数而不是容器类型(std::initializer_list)来收集枚举器,但是遇到了不同编译器的问题(对于非类型模板参数,表达式中明确禁止赋值)。 - dyp

3
基于问题开头给出的文章,我得出一个解决方案,假设您知道无效范围。
我真的想知道这是否是一个好的解决方案。
首先,以类似以下的方式结束枚举:CAPI_END = 60。这将有助于迭代。因此我的代码如下:
typedef enum {
    CAPI_SUBTYPE_NULL = 0,               /* Null subtype. */
    CAPI_SUBTYPE_DIAG_DFD = 1,           /* Data Flow diag. */
    CAPI_SUBTYPE_DIAG_ERD = 2,           /* Entity-Relationship diag. */
    CAPI_SUBTYPE_DIAG_STD = 3,           /* State Transition diag. */
    CAPI_SUBTYPE_DIAG_STC = 4,           /* Structure Chart diag. */
    CAPI_SUBTYPE_DIAG_DSD = 5,           /* Data Structure diag. */
    CAPI_SUBTYPE_SPEC_PROCESS = 6,       /* Process spec. */
    CAPI_SUBTYPE_SPEC_MODULE = 7,        /* Module spec. */
    CAPI_SUBTYPE_SPEC_TERMINATOR = 8,    /* Terminator spec. */

    CAPI_SUBTYPE_DD_ALL = 13,            /* DD Entries (All). */
    CAPI_SUBTYPE_DD_COUPLE = 14,         /* DD Entries (Couples). */
    CAPI_SUBTYPE_DD_DATA_AREA = 15,      /* DD Entries (Data Areas). */
    CAPI_SUBTYPE_DD_DATA_OBJECT = 16,    /* DD Entries (Data Objects). */
    CAPI_SUBTYPE_DD_FLOW = 17,           /* DD Entries (Flows). */
    CAPI_SUBTYPE_DD_RELATIONSHIP = 18,   /* DD Entries (Relationships). */
    CAPI_SUBTYPE_DD_STORE = 19,          /* DD Entries (Stores). */

    CAPI_SUBTYPE_DIAG_PAD = 35,          /* Physical architecture diagram. */
    CAPI_SUBTYPE_DIAG_BD  = 36,          /* Behaviour diagram. */
    CAPI_SUBTYPE_DIAG_UCD = 37,          /* UML Use case diagram. */
    CAPI_SUBTYPE_DIAG_PD  = 38,          /* UML Package diagram. */
    CAPI_SUBTYPE_DIAG_COD = 39,          /* UML Collaboration diagram. */
    CAPI_SUBTYPE_DIAG_SQD = 40,          /* UML Sequence diagram. */
    CAPI_SUBTYPE_DIAG_CD  = 41,          /* UML Class diagram. */
    CAPI_SUBTYPE_DIAG_SCD = 42,          /* UML State chart. */
    CAPI_SUBTYPE_DIAG_ACD = 43,          /* UML Activity chart. */
    CAPI_SUBTYPE_DIAG_CPD = 44,          /* UML Component diagram. */
    CAPI_SUBTYPE_DIAG_DPD = 45,          /* UML Deployment diagram. */
    CAPI_SUBTYPE_DIAG_PFD = 47,          /* Process flow diagram. */
    CAPI_SUBTYPE_DIAG_HIER = 48,         /* Hierarchy diagram. */
    CAPI_SUBTYPE_DIAG_IDEF0 = 49,        /* IDEF0 diagram. */
    CAPI_SUBTYPE_DIAG_AID = 50,          /* AID diagram. */
    CAPI_SUBTYPE_DIAG_SAD = 51,          /* SAD diagram. */
    CAPI_SUBTYPE_DIAG_ASG = 59,           /* ASG diagram. */
    CAPI_END = 60                        /* just to mark the end of your enum */
} CAPI_SUBTYPE_E ;

CAPI_SUBTYPE_E& operator++(CAPI_SUBTYPE_E& capi)
{
  const int ranges = 2;  // you have 2 invalid ranges in your example
  int invalid[ranges][2] = {{8, 12}, {19, 34}};  // {min, max} (inclusive, exclusive)

  CAPI_SUBTYPE_E next = CAPI_SUBTYPE_NULL;

  for (int i = 0; i < ranges; i++)
    if ( capi >= invalid[i][0] && capi < invalid[i][1] ) {
      next = static_cast<CAPI_SUBTYPE_E>(invalid[i][1] + 1);
      break;
    } else {
      next = static_cast<CAPI_SUBTYPE_E>(capi + 1);
    }

  //  if ( next > CAPI_END )
    // throw an exception

  return capi = next;
}

int main()
{
  for(CAPI_SUBTYPE_E i = CAPI_SUBTYPE_NULL; i < CAPI_END; ++i)
    cout << i << endl;

  cout << endl;
}

我只提供一个前置增量运算符。后置增量运算符待以后实现。


3
答案是“不可以在C++03或C++11中迭代枚举元素”。现在,你可以以一种可以在编译时理解的方式描述enum的值集合。
template<typename E, E... Es>
struct TypedEnumList {};

typedef TypedEnumList<
  CAPI_SUBTYPE_E,
  CAPI_SUBTYPE_NULL, // etc
  // ...
  CAPI_SUBTYPE_DIAG_ASG
> CAPI_SUBTYPE_E_LIST;

这会给你一个类型为CAPI_SUBTYPE_E_LIST的东西,它封装了enum值的列表。

然后我们可以轻松地用这些填充数组:

 template<typename T, T... Es>
 std::array<T, sizeof...(Es)> GetRuntimeArray( TypedEnumList<T, Es... > ) {
   return { Es... };
 }
 auto Capis = GetRuntimeArray( CAPI_SUBTYPE_E_LIST() );

如果你真的需要它。但这只是更一般情况的特例,即能够为你的每个enum CAPI_SUBTYPE_E元素生成代码--不需要直接构建for循环。
有趣的是,使用符合C++11标准的编译器,我们可以编写代码,如果那些元素实际上在CAPI_SUBTYPE_E中,则会生成具有特定enum元素的CAPI_SUBTYPE_E_LIST。这将非常有用,因为我们可以使用我们支持的最新版本的API,并且如果我们编译的API更为原始,它会自动降级(在编译时)。
为了演示这种技术,我将从一个玩具enum开始。
enum Foo { A = 0, /* B = 1 */ };

假设在最新版的API中取消注释B=1,而这个参数在较早的版本中并没有。
template<int index, typename EnumList, typename=void>
struct AddElementN: AddElementN<index-1, EnumList> {};
template<typename EnumList>
struct AddElementN<-1, EnumList, void> {
  typedef EnumList type;
};

template<typename Enum, Enum... Es>
struct AddElementN<0, TypedEnumList<Enum, Es...>, typename std::enable_if< Enum::A == Enum::A >::type >:
  AddElement<-1, TypedEnumList<Enum, A, Es...>>
{};
template<typename Enum, Enum... Es>
struct AddElementN<1, TypedEnumList<Enum, Es...>, typename std::enable_if< Enum::B == Enum::B >::type >:
  AddElement<0, TypedEnumList<Enum, B, Es...>>
{};
// specialize this for your enum to call AddElementN:
template<typename Enum>
struct BuildTypedList;
template<>
struct BuildTypedList<CAPI_SUBTYPE_E>:
  AddElementN<1, TypedEnumList<CAPI_SUBTYPE_E>>
{};
template<typename Enum>
using TypedList = typename BuildTypedList<Enum>::type;

现在,如果我理解正确的话,TypedList<CAPI_SUBTYPE_E>包含B,当且仅当B被定义为CAPI_SUBTYPE_E的一个元素。这允许您针对多个版本的库进行编译,并根据库中包含的内容在枚举元素列表中获取不同的元素集。您必须维护annoying boilerplate(可以通过宏或代码生成使其更容易),针对“final”版本的枚举元素,但它应该自动处理以前的版本在编译时。
不幸的是,这需要大量的维护才能工作。
最后,您对此的要求是动态的:这个实现动态的唯一实际方式是将第三方API封装在知道API版本的代码中,并暴露一个不同的enum值缓冲区(我会把它放在std::vector中),具体取决于API的版本。然后,在加载API时,您还会加载此辅助包装器,该包装器然后使用上述技术构建enum元素的集合,您可以迭代这些集合。
其中一些样板代码可以使用一些可怕的宏来更轻松地编写,例如通过使用__LINE__来索引递归类型来构建各种AddElementN类型SFINAE代码。但那将是可怕的。

3

使用一些增强预处理可以让内容更加清晰。

您可以通过一个序列来定义枚举。

#define CAPI_SUBTYPE_E_Sequence \
    (CAPI_SUBTYPE_NULL)(0)  \
    (CAPI_SUBTYPE_DIAG_DFD)(1) ...

然后您可以通过宏自动声明枚举,

DECL_ENUM(CAPI_SUBTYPE_E) ;

它对应的表格
DECL_ENUM_TABLE(CAPI_SUBTYPE_E);

枚举数量/表格大小

ENUM_SIZE(CAPI_SUBTYPE_E)

并且可以访问它:

ITER_ENUM_i(i,CAPI_SUBTYPE_E)

以下是完整的文本。

#include <boost/preprocessor.hpp>

// define your enum as (name)(value) sequence
#define CAPI_SUBTYPE_E_Sequence \
    (CAPI_SUBTYPE_NULL)(0)  /* Null subtype. */ \
    (CAPI_SUBTYPE_DIAG_DFD)(1) /* Data Flow diag. */ \
    (CAPI_SUBTYPE_DIAG_ERD)(2)  /* Entity-Relationship diag. */ \
    (CAPI_SUBTYPE_DIAG_DSD)(5) /* Data Structure diag. */ \
    (CAPI_SUBTYPE_DD_ALL)(13) /* DD Entries (All). */

//  # enums
#define ENUM_SIZE(name) \
    BOOST_PP_DIV(BOOST_PP_SEQ_SIZE(BOOST_PP_CAT(name,_Sequence)),2)

#define ENUM_NAME_N(N,seq) BOOST_PP_SEQ_ELEM(BOOST_PP_MUL(N,2),seq)
#define ENUM_VALUE_N(N,seq) BOOST_PP_SEQ_ELEM(BOOST_PP_INC(BOOST_PP_MUL(N,2)),seq) 

// declare Nth enum
#define DECL_ENUM_N(Z,N,seq) \
    BOOST_PP_COMMA_IF(N)   ENUM_NAME_N(N,seq) =  ENUM_VALUE_N(N,seq)

// declare whole enum
#define DECL_ENUM(name) \
    typedef enum { \
       BOOST_PP_REPEAT( ENUM_SIZE(name) , DECL_ENUM_N , BOOST_PP_CAT(name,_Sequence) ) \
       } name 

DECL_ENUM(CAPI_SUBTYPE_E) ;

// declare Nth enum value
#define DECL_ENUM_TABLE_N(Z,N,seq) \
    BOOST_PP_COMMA_IF(N)   ENUM_NAME_N(N,seq)

// declare table
#define DECL_ENUM_TABLE(name) \
    static const name BOOST_PP_CAT(name,_Table) [ENUM_SIZE(name)] = { \
       BOOST_PP_REPEAT( ENUM_SIZE(name) , DECL_ENUM_TABLE_N , BOOST_PP_CAT(name,_Sequence) ) \
       } 

DECL_ENUM_TABLE(CAPI_SUBTYPE_E);

#define ITER_ENUM_i(i,name)  BOOST_PP_CAT(name,_Table) [i] 

// demo 
// outputs :  [0:0] [1:1] [2:2] [3:5] [4:13]
#include <iostream>

int main() {
    for (int i=0; i<ENUM_SIZE(CAPI_SUBTYPE_E) ; i++)
        std::cout << "[" << i << ":" << ITER_ENUM_i(i,CAPI_SUBTYPE_E) << "] ";

    return 0;
}

// bonus : check enums are unique and in-order

#include <boost/preprocessor/stringize.hpp>
#include  <boost/static_assert.hpp>

      #define CHECK_ENUM_N(Z,N,seq) \
      BOOST_PP_IF( N , \
      BOOST_STATIC_ASSERT_MSG( \
            ENUM_VALUE_N(BOOST_PP_DEC(N),seq) < ENUM_VALUE_N(N,seq) , \
               BOOST_PP_STRINGIZE( ENUM_NAME_N(BOOST_PP_DEC(N),seq) ) " not < " BOOST_PP_STRINGIZE( ENUM_NAME_N(N,seq) ) ) \
               , ) ;

#define CHECK_ENUM(name) \
    namespace { void BOOST_PP_CAT(check_enum_,name) () { \
    BOOST_PP_REPEAT( ENUM_SIZE(name) , CHECK_ENUM_N , BOOST_PP_CAT(name,_Sequence) )  } }

// enum OK
CHECK_ENUM(CAPI_SUBTYPE_E)

#define Bad_Enum_Sequence \
    (one)(1)\
    (five)(5)\
    (seven)(7)\
    (three)(3)

// enum not OK : enum_iter.cpp(81): error C2338: seven not < three
CHECK_ENUM(Bad_Enum)

2

使用高阶宏

这是我们在项目中使用的技术。

概念:

这个想法是生成一个名为LISTING的宏,其中包含名称-值对的定义,并将另一个宏作为参数。在下面的示例中,我定义了两个这样的辅助宏。'GENERATE_ENUM'用于生成枚举,'GENERATE_ARRAY'用于生成可迭代数组。当然,如果需要,可以扩展此解决方案。我认为这种解决方案能够让你得到最大的回报。

从概念上讲,这与iammilind's solution非常相似。

例如:

// helper macros
#define GENERATE_ENUM(key,value)       \
      key = value                      \

#define GENERATE_ARRAY(name,value)     \
       name                            \

// Since this is C++, I took the liberty to wrap everthing in a namespace. 
// This done mostly for aesthetic reasons, you don't have to if you don't want.        
namespace CAPI_SUBTYPES 
{
    //  I define a macro containing the key value pairs
    #define LISTING(m)                 \ 
       m(NONE, 0),    /* Note: I can't use NULL here because it conflicts */
       m(DIAG_DFD, 1),                 \
       m(DIAG_ERD, 2),                 \
       ...
       m(DD_ALL, 13),                  \
       m(DD_COUPLE, 14),               \
       ...
               m(DIAG_SAD, 51),                \
       m(DIAG_ASG, 59),                \

    typedef enum {
       LISTING(GENERATE_ENUM)
    } Enum;

    const Enum At[] = {
       LISTING(GENERATE_ARRAY)
    };

    const unsigned int Count = sizeof(At)/sizeof(At[0]);
}

用法:
现在你可以在代码中这样引用枚举:
CAPI_SUBTYPES::Enum eVariable = CAPI_SUBTYPES::DIAG_STD;

您可以像这样遍历枚举:
for (unsigned int i=0; i<CAPI_SUBTYPES::Count;  i++) {
     ...
     CAPI_SUBTYPES::Enum eVariable = CAPI_SUBTYPES::At[i];
     ...
}

注意:
如果我没有记错的话,C++11 中的枚举类型是在它们自己的命名空间中(就像 Java 或 C# 一样),因此上面的用法不会起作用。你必须像这样引用枚举值:CAPI_SUBTYPES::Enum::FooBar。

2

1

涉及无宏和(几乎)无运行时开销的解决方案初步:

#include <iostream>
#include <utility>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/find.hpp>

template<int v> using has_value = std::integral_constant<int, v>;

template<class...EnumValues>
struct better_enum
{
    static constexpr size_t size = sizeof...(EnumValues);
    using value_array = int[size];
    static const value_array& values() {
        static const value_array _values = { EnumValues::value... };
        return _values;
    }
    using name_array = const char*[size];
    static const name_array& names() {
        static const name_array _names = { EnumValues::name()... };
        return _names;
    }


    using enum_values = boost::mpl::vector<EnumValues...>;

    struct iterator {
        explicit iterator(size_t i) : index(i) {}

        const char* name() const {
            return names()[index];
        }
        int value() const {
            return values()[index];
        }
        operator int() const {
            return value();
        }

        void operator++() {
            ++index;
        }
        bool operator==(const iterator& it) const {
            return index == it.index;
        }
        bool operator!=(const iterator& it) const {
            return index != it.index;
        }
        const iterator& operator*() const {
            return *this;
        }
    private:
        size_t index;
    };
    friend std::ostream& operator<<(std::ostream& os, const iterator& iter)
    {
        os << "{ " << iter.name() << ", " << iter.value() << " }";
        return os;
    }

    template<class EnumValue>
    static iterator find() {
        using iter = typename boost::mpl::find<enum_values, EnumValue>::type;
        static_assert(iter::pos::value < size, "attempt to find a value which is not part of this enum");
        return iterator { iter::pos::value };
    }

    static iterator begin() {
        return iterator { 0 };
    }

    static iterator end() {
        return iterator { size };
    }

};

struct Pig : has_value<0> { static const char* name() { return "Pig";} };
struct Dog : has_value<7> { static const char* name() { return "Dog";} };
struct Cat : has_value<100> { static const char* name() { return "Cat";} };
struct Horse : has_value<90> { static const char* name() { return "Horse";} };

struct Monkey : has_value<1000> { static const char* name() { return "Monkey";} };

using animals = better_enum<
Pig,
Dog,
Cat,
Horse
>;

using namespace std;

auto main() -> int
{
    cout << "size : " << animals::size << endl;
    for (auto v : animals::values())
    cout << v << endl;

    for (auto v : animals::names())
    cout << v << endl;

    cout << "full iteration:" << endl;
    for (const auto& i : animals())
    {
        cout << i << endl;
    }

    cout << "individials" << endl;
    auto animal = animals::find<Dog>();
    cout << "found : " << animal << endl;
    while (animal != animals::find<Horse>()) {
        cout << animal << endl;
        ++animal;
    }

// will trigger the static_assert    auto xx = animals::find<Monkey>();

    return 0;
}

输出:

size : 4
0
7
100
90
Pig
Dog
Cat
Horse
full iteration:
{ Pig, 0 }
{ Dog, 7 }
{ Cat, 100 }
{ Horse, 90 }
individials
found : { Dog, 7 }
{ Dog, 7 }
{ Cat, 100 }

1
将它们放入数组或其他容器中,然后对其进行迭代。如果您修改了枚举,则必须更新将其放入容器的代码。

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