38 votes

Itération sur Enum non incrémental

Avant de vous demander, j'ai regardé et regardé pour cette SORTE, et ne peut pas trouver une réponse solide.

J'ai besoin d'être en mesure de dynamiquement itérer sur un enum qui a des non-valeurs incrémentielles, à titre d'exemple:

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 ;

La raison pour laquelle je voudrais pouvoir faire, c'est parce que l'enum est donné dans une API (qui je ne peux pas changer, évidemment) et que vous préférez être en mesure, quelle que soit la version de l'API, être en mesure d'effectuer une itération sur ces valeurs.

N'importe quelle direction est apprécié.

16voto

iammilind Points 29275

Avec le C++, le seul moyen pour itérer sur les enums est de les stocker dans un tableau et parcourir la même. Le principal défi est de savoir comment suivre le même ordre dans l' enum déclaration et la déclaration de tableau?
Vous pouvez automatiser la façon dont vous les commander en enum ainsi que tableau. J'ai l'impression que c'est une manière décente:

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

Maintenant, vous #include ce fichier dans votre déclaration d'enum et de déclaration de tableau à la fois des lieux avec macro redéfinition:

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

Et de mettre le fichier de même pour le tableau avec l'autre définition de macro:

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

Maintenant itérer en C++03:

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

ou encore simple en C++11:

for(auto i : CAPI_SUBTYPE_E_Array)

11voto

fasked Points 2226

Il s’agit de techniques délicates et plus complexes que la pratique C ++, mais vous pouvez utiliser des macros X. C'est très moche et vous devez garder TABLE dans le bon ordre. En C ++, je pense que nous n'avons pas besoin de parcourir les énumérations et que nous n'avons pas besoin d'attribuer de valeurs à une énumération (la valeur apparente de l'énumération est aléatoire dans chaque compilation). Alors pensez-y comme une blague :)

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

7voto

dyp Points 19641

Je suis d'accord avec le déjà donné des déclarations que ce n'est pas possible sans que ce soit de modifier ou de copier les définitions de l' enum. Toutefois, en C++11 (peut-être même en C++03?) vous pouvez aller aussi loin que de fournir une syntaxe où tout ce que vous avez à faire (littéralement) est de copier et coller l'agent recenseur définitions de l' enum dans une macro. Cela fonctionne tant que chaque agent recenseur a une définition explicite (à l'aide d' =).

Edit: Vous pouvez étendre ce travail, même si pas chaque agent recenseur a une définition explicite, mais cela ne devrait pas être nécessaire dans ce cas.

J'ai une fois développé pour certains physiciens, afin que l'exemple est sur les particules.

Exemple d'utilisation:

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

La macro rendements (efficace):

namespace particle_enum_detail
{
    // definition of a type and some constants

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

définition de macro

#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

Remarque: le type iterable_enum_ pourrait ainsi être défini une fois à l'extérieur de la macro.


macro explication

L'idée est de permettre à une syntaxe comme celle - proton = 11, electron = 12 au sein de la macro invocation. Cela fonctionne très facile pour n'importe quel type de déclaration, mais il crée des problèmes pour stocker les noms de:

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

les rendements à:

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

Comme avec beaucoup de syntaxe astuces, la surcharge d'opérateur fournit un moyen de surmonter ce problème; mais l'opérateur d'affectation doit être un membre de fonctions et les énumérations ne sont pas des classes. Alors pourquoi ne pas utiliser certains objets constants au lieu d'un enum?

enum my_enum { proton = 11, electron = 22 };
// alternatively
constexpr int proton = 11, electron = 12;
// the `constexpr` here is equivalent to a `const`

Ce n'est pas encore résoudre notre problème, il démontre simplement que nous pouvons facilement remplacer les énumérations, par une liste de constantes si nous n'avons pas besoin de l'auto-incrément de fonction des agents recenseurs.

Maintenant, la syntaxe truc avec la surcharge d'opérateur:

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)

Le truc, c'est, dans la ligne (1) = désigne une copie de l'initialisation, qui est fait en convertissant le nombre (11, 22) pour un temporaire de type particle par l'aide de la (ctor) et la copie/le déplacement temporaire par le biais d'un implicitement défini ctor à l'objet de destination (proton, electron).

En revanche, l' = en ligne (2) est résolu à un opérateur d'appel de (op), qui renvoie une copie de l'objet sur lequel il a été appelé (*this). L' constexpr trucs permet d'utiliser ces variables au moment de la compilation, par exemple, dans un modèle de déclaration. En raison de restrictions sur constexpr fonctions, nous ne pouvons tout simplement pas de retour *this dans le (op) de la fonction. En outre, constexpr implique toutes les restrictions d' const.

En fournissant une conversion implicite de l'opérateur, vous pouvez créer le tableau en ligne (2) de type ParticleEnum:

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

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

3voto

Tomás Badan Points 2051

Sur la base des articles donnés au début de la question, j'ai trouvé une solution basée sur l'hypothèse que vous connaissez les plages d'invalides.

Je veux vraiment savoir si c'est une bonne solution.

Tout d’abord, terminez avec quelque chose comme ça: CAPI_END = 60 . Cela aidera à interagir. Donc mon code est:

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

Je ne fournis qu'un opérateur de pré-incrémentation. Un opérateur post-incrément est laissé pour être implémenté plus tard.

3voto

Yakk Points 31636

La réponse est "non, vous ne pouvez pas effectuer une itération sur les éléments d'un enum en C++03 ou C++11".

Maintenant, vous pouvez décrire l'ensemble des valeurs d'un enum d'une manière qui peut être comprise au moment de la compilation.

template<typename E, E... Es>
struct TypedEnumList {};

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

ce qui vous donne un type CAPI_SUBTYPE_E_LIST qui encapsule la liste d' enum valeurs.

On peut alors remplir un tableau avec ces facilement:

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

si vous en avez vraiment besoin. Mais ce n'est qu'un cas particulier du cas plus général d'être capable de générer du code pour chaque élément de votre enum CAPI_SUBTYPE_E -- directement la construction d'un for boucle n'est pas nécessaire.

De manière amusante, avec un compatible C++11 compilateur, nous pourrions écrire du code qui permettrait de générer notre CAPI_SUBTYPE_E_LIST notamment enum éléments iff ces éléments sont réellement en CAPI_SUBTYPE_E à l'aide de SFINAE. Ce serait utile parce qu'on peut prendre la version la plus récente de l'API, nous pouvons soutenir, et faire l'auto-dégrader (au moment de la compilation) si l'API, nous compilons contre est plus primitif.

Pour démontrer la technique, je vais commencer avec un jouet enum

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

Imaginez qu' B=1 est retirée dans la version plus moderne de l'API, mais il n'est pas dans la plus primitive.

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;

maintenant, si j'ai écrit ce droit, TypedList<CAPI_SUBTYPE_E> contient B mfi B est défini comme un élément de l' CAPI_SUBTYPE_E. Cela vous permet de compiler à l'encontre de plus d'une version de la bibliothèque, et obtenir un autre ensemble d'éléments dans votre enum élément de la liste en fonction de ce qui est dans la bibliothèque. Vous n'avez pas à maintenir ce gênant passe-partout (ce qui pourrait probablement être plus facile avec des macros ou de génération de code) à l'encontre de la "dernière" version de l' enums éléments, mais il doit gérer automatiquement les versions précédentes au moment de la compilation.

Cette tristement nécessite beaucoup d'entretien pour fonctionner.

Enfin, votre exigence que cette dynamique: le seul moyen pratique de cette dynamique est d'envelopper la 3ème partie de l'API dans le code qui sait ce que la version de l'API est, et expose un autre tampon de enum valeurs (je l'avais mis dans un std::vector), selon ce que la version de l'API. Puis, quand vous chargez de l'API, vous charge de cette aide wrapper, qui utilise ensuite les techniques ci-dessus pour construire l'ensemble des éléments de l' enum, ce qui vous parcourez.

Certains de ce standard peut être plus facile d'écrire avec une horrible macros, comme ceux qui construisent les différentes AddElementN type SFINAE code à l'aide de l' __LINE__ de l'indice des types récursifs. Mais ce serait horrible.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X