Подтвердить что ты не робот

Итерация по неинкрементному Enum

Прежде чем спросить, я посмотрел и посмотрел для этого на 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 мог выполнять итерацию над этими значениями.

Любое направление оценивается.

4b9b3361

Ответ 1

С С++ единственный способ итерации через enums - хранить их в массиве и перебирать то же самое. Основная задача заключается в том, как отслеживать один и тот же порядок в объявлении 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 и объявлении массива оба места с переопределением макроса:

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

Теперь перейдите в С++ 03 как:

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

или еще просто в С++ 11:

for(auto i : CAPI_SUBTYPE_E_Array)

Ответ 2

Это сложная и более C-практика, чем С++, но вы можете использовать макросы X. Это очень уродливо, и вам нужно сохранить ТАБЛИЦУ в правильном порядке. В С++ я считаю, что нам не нужно перебирать перечисления и больше нам не нужно назначать значения для перечисления (якобы значение перечисления случайное в каждой компиляции). Поэтому подумайте об этом как о шутке:)

#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

Я согласен с уже приведенными утверждениями, что это невозможно без изменения или копирования определений enum. Однако в С++ 11 (возможно, даже С++ 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 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?

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)

Фокус в строке (1): = обозначает инициализацию копирования, которая выполняется путем преобразования числа (11, 22) во временный тип particle с помощью ( ctor) и копирование/перемещение временного с помощью неявно заданного ctor целевому объекту (proton, electron).

Напротив, = в строке (2) разрешается вызов оператора (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 };

Ответ 4

На основании статей, приведенных в начале вопроса, я получил решение, основанное на предположении, что вы знаете диапазоны инвалидности.

Я действительно хочу знать, является ли это хорошим решением.

Во-первых, закончите перечислять что-то вроде этого: 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;
}

Я предоставляю только оператор pre increment. Оператор приращения сообщения позволяет впоследствии быть реализован.

Ответ 5

Ответ: "нет, вы не можете перебирать элементы enum в С++ 03 или С++ 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 не требуется.

Смешно, с совместимым компилятором С++ 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> содержит B iff B определяется как элемент CAPI_SUBTYPE_E. Это позволяет вам скомпилировать более чем одну версию библиотеки и получить другой набор элементов в списке элементов enum в зависимости от того, что находится в библиотеке. Вам нужно поддерживать этот раздражающий шаблон (который, вероятно, может быть проще с макросами или генерацией кода) против "окончательной" версии элементов enum, но он должен автоматически обрабатывать предыдущие версии во время компиляции.

Это, к сожалению, требует много обслуживания для работы.

Наконец, ваше требование, чтобы это было динамическим: единственный практический способ для этого быть динамичным - обернуть сторонний API в код, который знает, что представляет собой версия API, и предоставляет другой буфер значений enum (Я бы поставил его в std::vector) в зависимости от версии API. Затем, когда вы загружаете API, вы также загружаете эту вспомогательную оболочку, которая затем использует вышеуказанные методы для сборки набора элементов enum, который вы перебираете.

Некоторые из этих шаблонов можно упростить для записи с помощью некоторых ужасных макросов, таких как те, которые создают код типа SFINAE типа AddElementN, используя __LINE__ для индексации рекурсивных типов. Но это было бы ужасно.

Ответ 6

Вы не можете перебирать произвольный enum в С++. Для итерации значения должны быть помещены в некоторый контейнер. Вы можете автоматизировать сохранение такого контейнера с использованием "классов enum", как описано здесь: http://www.drdobbs.com/when-enum-just-isnt-enough-enumeration-c/184403955http://www.drdobbs.com/when-enum-just-isnt-enough-enumeration-c/184403955

Ответ 7

Несколько более ясный (???) с некоторой предварительной обработкой форсирования.

Вы определяете свои перечисления с помощью последовательности

#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)

Ответ 8

Использовать макросы более высокого порядка

Вот техника, которую мы использовали в наших проектах.

Концепция:

Идея состоит в создании макроса под названием LISTING, который содержит определение пар имя-значение, и в качестве аргумента требуется другой макрос. В приведенном ниже примере я определил два таких вспомогательных макроса. "GENERATE_ENUM" для генерации перечисления и "GENERATE_ARRAY" для создания итерационного массива. Конечно, это может быть расширено по мере необходимости. Я думаю, что это решение дает вам больше всего шансов для доллара. Концептуально он очень похож на iammilind 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];
     ...
}

Примечание:

Если память мне подходит, С++ 11 перечислены в собственных пространствах имен (например, на Java или С#), поэтому вышеупомянутое использование не будет работать. Вам нужно будет ссылаться на значения перечисления, подобные этому CAPI_SUBTYPES:: Enum:: FooBar.

Ответ 9

начало решения без макросов и (почти) без накладных расходов во время выполнения:

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

Ответ 10

Поместите их в массив или другой контейнер и повторите это. Если вы измените перечисление, вам придется обновить код, который помещает их в контейнер.

Ответ 11

Вот еще один подход. Один из бонусов заключается в том, что ваш компилятор может предупредить вас, если вы опустите значение перечисления в 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;
}

Ответ 12

Поскольку перечисление не позволяет итерации, вам нужно создать альтернативное представление перечисления и его диапазон значений.

Подход, который я бы использовал, будет простым просмотром таблицы, встроенным в класс. Проблема заключается в том, что, поскольку API изменяет свое перечисление на новые записи, вам также потребуется обновить конструктор для этого класса.

Простой класс, который я использовал бы, будет состоять из конструктора для построения таблицы вместе с несколькими методами для итерации по таблице. Поскольку вы также хотите знать, есть ли проблема с размером таблицы при добавлении элементов, вы можете использовать макрос assert (), который выдает assert() в режиме отладки. В приведенном ниже примере источника я использую препроцессор для проверки отладки компиляции или нет и включен ли assert или нет, чтобы обеспечить механизм для проверки базовой согласованности.

Я заимствовал идею, которую я видел у П. Дж. Плагера в своей книге Standard C Library, используя простую таблицу поиска для операций символа 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 по мере их изменения. К сожалению, не так много можно сделать для автоматизации проверки обновления, но у нас есть тесты на месте отладка компиляции, чтобы проверить, что таблица достаточно велика и что значение перечисления, которое помещается в таблицу, находится в пределах диапазона размера таблицы.

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

Ответ 13

Я использую этот тип конструкций для определения собственных перечислений:

#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_ на boost::bimap, чтобы иметь двунаправленную корреспонденцию int < == > string

Ответ 14

Уже есть много ответов на этот вопрос, но большинство из них либо очень сложны, либо неэффективны в том смысле, что они прямо не учитывают требование итерации по перечислению с пробелами. Все до сих пор говорили, что это невозможно, и они являются правильными в том смысле, что не существует языковой функции, позволяющей вам это делать. Это, конечно, не означает, что вы не можете, и, как мы можем видеть по всем ответам, существует много разных способов сделать это. Вот мой путь, основанный на перечислении, которое вы предоставили, и предположение, что структура не сильно изменится. Конечно, этот метод можно при необходимости адаптировать.

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

Или что-то в этом роде.

Ответ 15

Единственное реальное "решение", которое я, наконец, придумал для решения этой проблемы, - создать предварительный запуск script, который читает файлы c/С++, содержащие перечисления, и генерирует файл класса, который имеет список всех перечислений в виде векторов. Это очень похоже на то, как Visual Studio поддерживает T4 Templates. В мире .Net это довольно распространенная практика, но поскольку я не могу работать в этой среде, я был вынужден сделать это таким образом.

script Я написал это в Ruby, но вы можете сделать это на любом языке. Если кто-то хочет источник script, я загрузил его здесь. Это отнюдь не идеальный script, но он соответствует счету для моего проекта. Я призываю кого-то улучшить его и дать советы здесь.