38

在你问之前,我已经在 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

15 回答 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)
于 2013-05-19T02:52:24.650 回答
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
}
于 2013-05-16T20:43:49.523 回答
7

我同意已经给出的陈述,即如果不更改或复制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);

产生于:

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`

这还没有解决我们的问题,它只是证明如果我们不需要 enumerators 的自动增量功能,我们可以很容易地用常量列表替换 enums 。

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

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)

诀窍是,在第 (1) 行中,=指定了复制初始化,这是通过使用 (ctor) 将数字 ( 11, 22) 转换为临时类型particle并通过隐式定义的 ctor 复制/移动临时来完成的目标对象 ( proton, electron)。

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

通过提供隐式转换运算符,您可以在第 (2) 行中创建类型为 的数组ParticleEnum

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

// in namespace particle_enum_detail
ParticleEnum all_particles[] = { proton = 11, electron = 22 };
于 2013-05-18T21:19:32.867 回答
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)
于 2013-05-21T17:01:49.447 回答
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;
}

我只提供一个预增量运算符。后增量运算符允许稍后实现。

于 2013-05-20T02:40:18.937 回答
3

答案是“不,你不能迭代enumC++03 或 C++11 中的元素”。

现在,您可以enum以在编译时可以理解的方式描述 an 的值集。

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_LIST带有特定enum元素的代码,前提是这些元素实际上在CAPI_SUBTYPE_E使用 SFINAE。这将很有用,因为我们可以采用我们可以支持的最新版本的 API,如果我们编译的 API 更原始,它会自动降级(在编译时)。

为了演示这项技术,我将从一个玩具开始enum

enum Foo { A = 0, /* B = 1 */ };

想象一下,B=1在最现代的 API 版本中没有注释,但在更原始的版本中没有。

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>contains BiffB被定义为CAPI_SUBTYPE_E. 这使您可以针对多个版本的库进行编译,并根据库中的内容在enum元素列表中获取一组不同的元素。您确实必须针对 s 元素的“最终”版本维护那个烦人的样板(可能通过宏或代码生成更容易)enum,但它应该在编译时自动处理以前的版本。

遗憾的是,这需要大量维护才能工作。

最后,您要求这是动态的:使其成为动态的唯一实用方法是将第 3 方 API 包装在知道 API 版本是什么的代码中,并公开不同的enum值缓冲区(我会说a std::vector) 取决于 API 的版本。然后,当您加载 API 时,您还加载了这个辅助包装器,然后它使用上述技术构建enum您迭代的元素集。

使用一些可怕的宏可以使这些样板中的一些更容易编写,例如通过使用索引递归类型来构建各种类型AddElementN的 SFINAE 代码的那些。__LINE__但这将是可怕的。

于 2013-05-21T03:26:56.640 回答
2

您不能enum在 C++ 中任意迭代。对于迭代,值应该放在某个容器中。您可以使用此处所述的“枚举类”自动维护此类容器:http ://www.drdobbs.com/when-enum-just-isnt-enough-enumeration-c/184403955http://www.drdobbs.com/ when-enum-just-isnt-enough-enumeration-c/184403955

于 2013-05-16T20:18:03.487 回答
2

使用高阶宏

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

概念:

这个想法是生成一个名为 LISTING 的宏,其中包含名称-值对的定义,并将另一个宏作为参数。在下面的示例中,我定义了两个这样的辅助宏。“GENERATE_ENUM”生成枚举,“GENERATE_ARRAY”生成可迭代数组。当然,这可以根据需要进行扩展。我认为这个解决方案可以为您带来最大的收益。从概念上讲,它与iammilind 的解决方案非常相似。

例子:

// 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

于 2013-05-23T11:58:10.743 回答
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 }
于 2015-06-24T23:58:14.217 回答
0

这个问题已经有很多答案,但其中大多数要么非常复杂,要么效率低下,因为它们没有直接解决迭代有间隙的枚举的要求。到目前为止,每个人都说这是不可能的,他们是正确的,因为没有语言功能可以让你这样做。这当然并不意味着你不能,而且正如我们到目前为止的所有答案所看到的那样,有很多不同的方法可以做到这一点。这是我的方式,基于您提供的枚举以及它的结构不会发生太大变化的假设。当然,这种方法可以根据需要进行调整。

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;

struct ranges_t
{
    int start;
    int end;
};
ranges_t ranges[] =
{
    {CAPI_SUBTYPE_NULL,         CAPI_SUBTYPE_NULL},
    {CAPI_SUBTYPE_DIAG_DFD,     CAPI_SUBTYPE_DIAG_DSD},
    {CAPI_SUBTYPE_SPEC_PROCESS, CAPI_SUBTYPE_SPEC_TERMINATOR},
    {CAPI_SUBTYPE_DD_ALL,       CAPI_SUBTYPE_DD_STORE},
    {CAPI_SUBTYPE_DIAG_PAD,     CAPI_SUBTYPE_DIAG_SAD},
    {CAPI_SUBTYPE_DIAG_ASG,     CAPI_SUBTYPE_DIAG_ASG},
};
int numRanges = sizeof(ranges) / sizeof(*ranges);

for( int rangeIdx = 0; rangeIdx < numRanges; ++rangeIdx )
{
    for( int enumValue = ranges[rangeIdx].start; enumValue <= ranges[rangeIdx].end; ++enumValue )
    {
        processEnumValue( enumValue );
    }
}

或类似的规定。

于 2013-05-23T06:55:27.197 回答
0

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

于 2013-05-16T20:11:14.053 回答
0

我正在使用这种类型的结构来定义我自己的枚举:

#include <boost/unordered_map.hpp>

namespace enumeration
{

   struct enumerator_base : boost::noncopyable
   {
      typedef
         boost::unordered_map<int, std::string>
         kv_storage_t;
      typedef
         kv_storage_t::value_type
         kv_type;
      typedef
         std::set<int>
         entries_t;
      typedef
         entries_t::const_iterator
         iterator;
      typedef
         entries_t::const_iterator
         const_iterator;
      kv_storage_t const & kv() const
      {
         return storage_;
      }

      const char * name(int i) const
      {
         kv_storage_t::const_iterator it = storage_.find(i);
         if(it != storage_.end())
            return it->second.c_str();
         return "empty";
      }

      iterator begin() const
      {
         return entries_.begin();
      }

      iterator end() const
      {
         return entries_.end();
      }

      iterator begin()
      {
         return entries_.begin();
      }

      iterator end()
      {
         return entries_.end();
      }

      void register_e(int val, std::string const & desc)
      {
         storage_.insert(std::make_pair(val, desc));
         entries_.insert(val);
      }
   protected:
      kv_storage_t storage_;
      entries_t entries_;
   };

   template<class T>
   struct enumerator;

   template<class D>
   struct enum_singleton : enumerator_base
   {
      static enumerator_base const & instance()
      {
         static D inst;
         return inst;
      }
   };
}

#define QENUM_ENTRY(K, V, N)  K, N register_e((int)K, V);
#define QENUM_ENTRY_I(K, I, V, N)  K = I, N register_e((int)K, V);

#define QBEGIN_ENUM(NAME, C)   \
enum NAME                     \
{                             \
   C                          \
}                             \
};                            \
}                             \

#define QEND_ENUM(NAME) \
};                     \
namespace enumeration  \
{                      \
template<>             \
struct enumerator<NAME>\
   : enum_singleton< enumerator<NAME> >\
{                      \
   enumerator()        \
   {


QBEGIN_ENUM(test_t,
   QENUM_ENTRY(test_entry_1, "number uno",
   QENUM_ENTRY_I(test_entry_2, 10, "number dos",
   QENUM_ENTRY(test_entry_3, "number tres",
QEND_ENUM(test_t)))))


int _tmain(int argc, _TCHAR* argv[])
{
   BOOST_FOREACH(int x, enumeration::enumerator<test_t>::instance())
      std::cout << enumeration::enumerator<test_t>::instance().name(x) << "=" << x << std::endl;
   return 0;
}

您也可以将storage_type替换boost::bimap为具有双向对应 int <==> string

于 2013-05-21T17:11:10.160 回答
0

我最终想出的唯一真正的“解决方案”是创建一个预运行脚本,该脚本读取包含枚举的 c/c++ 文件并生成一个包含所有枚举列表的类文件向量。这与 Visual Studio 支持T4 模板的方式非常相似。在 .Net 世界中,这是很常见的做法,但由于我无法在那种环境中工作,所以我不得不这样做。

我编写的脚本是用 Ruby 编写的,但你可以用任何语言编写。如果有人想要源脚本,我在这里上传。这绝不是一个完美的脚本,但它适合我的项目。我鼓励任何人改进它并在这里给出提示。

于 2013-05-25T20:02:23.377 回答
0

这是另一种方法。一个好处是,如果您在 a 中省略枚举值,编译器可能会警告您switch

template<typename T>
void IMP_Apply(const int& pSubtype, T& pApply) {
    switch (pSubtype) {
        case CAPI_SUBTYPE_NULL :
        case CAPI_SUBTYPE_DIAG_DFD :
        case CAPI_SUBTYPE_DIAG_ERD :
        case CAPI_SUBTYPE_DIAG_STD :
        case CAPI_SUBTYPE_DIAG_STC :
        case CAPI_SUBTYPE_DIAG_DSD :
        case CAPI_SUBTYPE_SPEC_PROCESS :
        case CAPI_SUBTYPE_SPEC_MODULE :
        case CAPI_SUBTYPE_SPEC_TERMINATOR :
        case CAPI_SUBTYPE_DD_ALL :
        case CAPI_SUBTYPE_DD_COUPLE :
        case CAPI_SUBTYPE_DD_DATA_AREA :
        case CAPI_SUBTYPE_DD_DATA_OBJECT :
        case CAPI_SUBTYPE_DD_FLOW :
        case CAPI_SUBTYPE_DD_RELATIONSHIP :
        case CAPI_SUBTYPE_DD_STORE :
        case CAPI_SUBTYPE_DIAG_PAD :
        case CAPI_SUBTYPE_DIAG_BD :
        case CAPI_SUBTYPE_DIAG_UCD :
        case CAPI_SUBTYPE_DIAG_PD :
        case CAPI_SUBTYPE_DIAG_COD :
        case CAPI_SUBTYPE_DIAG_SQD :
        case CAPI_SUBTYPE_DIAG_CD :
        case CAPI_SUBTYPE_DIAG_SCD :
        case CAPI_SUBTYPE_DIAG_ACD :
        case CAPI_SUBTYPE_DIAG_CPD :
        case CAPI_SUBTYPE_DIAG_DPD :
        case CAPI_SUBTYPE_DIAG_PFD :
        case CAPI_SUBTYPE_DIAG_HIER :
        case CAPI_SUBTYPE_DIAG_IDEF0 :
        case CAPI_SUBTYPE_DIAG_AID :
        case CAPI_SUBTYPE_DIAG_SAD :
        case CAPI_SUBTYPE_DIAG_ASG :
            /* do something. just `applying`: */
            pApply(static_cast<CAPI_SUBTYPE_E>(pSubtype));
            return;
    }

    std::cout << "Skipped: " << pSubtype << '\n';
}

template<typename T>
void Apply(T& pApply) {
    const CAPI_SUBTYPE_E First(CAPI_SUBTYPE_NULL);
    const CAPI_SUBTYPE_E Last(CAPI_SUBTYPE_DIAG_ASG);

    for (int idx(static_cast<int>(First)); idx <= static_cast<int>(Last); ++idx) {
        IMP_Apply(idx, pApply);
    }
}

int main(int argc, const char* argv[]) {
    class t_apply {
    public:
        void operator()(const CAPI_SUBTYPE_E& pSubtype) const {
            std::cout << "Apply: " << static_cast<int>(pSubtype) << '\n';
        }
    };
    t_apply apply;
    Apply(apply);
    return 0;
}
于 2013-05-19T04:51:58.290 回答
0

由于枚举不允许迭代,因此您必须创建枚举及其值范围的替代表示。

我将采用的方法是嵌入类中的简单表查找。问题在于,当 API 使用新条目修改其枚举时,您还需要更新此类的构造函数。

我将使用的简单类将包含一个构建表的构造函数以及几个迭代表的方法。由于您还想知道添加项目时表大小是否存在问题,您可以使用会在调试模式下assert ()发出的宏。assert()在下面的源代码示例中,我使用预处理器来测试是否调试编译以及是否包含断言,以便提供基本一致性检查的机制。

我借用了 PJ Plauger 在他的《标准 C 库》一书中看到的一个想法,即使用简单的查找表进行 ANSI 字符操作,其中字符用于索引表。

要使用此类,您将执行以下操作,使用for循环遍历表中的值集。在循环体内,您可以对枚举值执行任何操作。

CapiEnum myEnum;

for (CAPI_SUBTYPE_E jj = myEnum.Begin(); !myEnum.End(); jj = myEnum.Next()) {
     // do stuff with the jj enum value
}

由于这个类枚举了值,所以我任意选择CAPI_SUBTYPE_NULL在我们已经到达枚举末尾的情况下返回值。 因此,在查找表错误的情况下,返回值在有效范围内,但不能依赖它。 因此,应该检查End()方法以查看是否已达到迭代的结尾。同样在完成对象的构造之后,可以检查m_bTableError数据成员以查看构造期间是否存在错误。

该类的源示例如下。 您需要在 API 更改时使用 API 的枚举值更新构造函数。 不幸的是,无法自动检查更新枚举,但我们确实在调试编译中进行了测试,以检查表是否足够大,以及放入表中的枚举值是否在表大小范围。

class CapiEnum {
public:
    CapiEnum (void);                                // constructor
    CAPI_SUBTYPE_E  Begin (void);                   // method to call to begin an iteration
    CAPI_SUBTYPE_E  Next (void);                    // method to get the next in the series of an iteration
    bool            End (void);                     // method to indicate if we have reached the end or not
    bool            Check (CAPI_SUBTYPE_E value);   // method to see if value specified is in the table
    bool  m_TableError;
private:
    static const int m_TableSize = 256;    // set the lookup table size
    static const int m_UnusedTableEntry = -1;
    int   m_iIterate;
    bool  m_bEndReached;
    CAPI_SUBTYPE_E  m_CapiTable[m_TableSize];
};

#if defined(_DEBUG)
#if defined(assert)
#define ADD_CAPI_ENUM_ENTRY(capi) (((capi) < m_TableSize && (capi) > m_UnusedTableEntry) ? (m_CapiTable[(capi)] = (capi)) : assert(((capi) < m_TableSize) && ((capi) > m_UnusedTableEntry)))
#else
#define ADD_CAPI_ENUM_ENTRY(capi) (((capi) < m_TableSize && (capi) > m_UnusedTableEntry) ? (m_CapiTable[(capi)] = (capi)) : (m_TableError = true))
#endif
#else
#define ADD_CAPI_ENUM_ENTRY(capi) (m_CapiTable[(capi)] = (capi))
#endif

CapiEnum::CapiEnum (void) : m_bEndReached(true), m_iIterate(0), m_TableError(false)
{
    for (int iLoop = 0; iLoop < m_TableSize; iLoop++) m_CapiTable[iLoop] = static_cast <CAPI_SUBTYPE_E> (m_UnusedTableEntry);
    ADD_CAPI_ENUM_ENTRY(CAPI_SUBTYPE_NULL);
    // .....
    ADD_CAPI_ENUM_ENTRY(CAPI_SUBTYPE_DIAG_ASG);
}

CAPI_SUBTYPE_E CapiEnum::Begin (void)
{
    m_bEndReached = false;
    for (m_iIterate = 0; m_iIterate < m_TableSize; m_iIterate++) {
        if (m_CapiTable[m_iIterate] > m_UnusedTableEntry) return m_CapiTable[m_iIterate];
    }
    m_bEndReached = true;
    return CAPI_SUBTYPE_NULL;
}

CAPI_SUBTYPE_E CapiEnum::Next (void)
{
    if (!m_bEndReached) {
        for (m_iIterate++; m_iIterate < m_TableSize; m_iIterate++) {
            if (m_CapiTable[m_iIterate] > m_UnusedTableEntry) return m_CapiTable[m_iIterate];
        }
    }
    m_bEndReached = true;
    return CAPI_SUBTYPE_NULL;
}

bool CapiEnum::End (void)
{
    return m_bEndReached;
}

bool CapiEnum::Check (CAPI_SUBTYPE_E value)
{
    return (value >= 0 && value < m_TableSize && m_CapiTable[value] > m_UnusedTableEntry);
}

如果你愿意,你可以添加一个额外的方法来检索迭代的当前值。请注意,Current() 方法不是递增到下一个,而是使用迭代索引当前所在的任何位置并从当前位置开始搜索。因此,如果当前位置是一个有效值,它只会返回它,否则它将找到第一个有效值。或者,您可以只返回索引指向的当前表值,如果该值无效,则设置一个错误指示器。

CAPI_SUBTYPE_E CapiEnum::Current (void)
{
    if (!m_bEndReached) {
        for (m_iIterate; m_iIterate < m_TableSize; m_iIterate++) {
            if (m_CapiTable[m_iIterate] > m_UnusedTableEntry) return m_CapiTable[m_iIterate];
        }
    }
    m_bEndReached = true;
    return CAPI_SUBTYPE_NULL;
}
于 2013-05-19T13:18:56.400 回答