Question

Voici ce que j'essaie de faire:

typedef enum { ONE, TWO, THREE } Numbers;

J'essaie d'écrire une fonction qui ferait un cas de commutation similaire à celui-ci :

char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, num); //some way to get the symbolic constant name in here?
    } break;
    default:
      return 0; //no match
  return 1;
}

Au lieu de définir à chaque cas, existe-t-il un moyen de le définir en utilisant la variable enum comme j'essaie de le faire ci-dessus ?

Était-ce utile?

La solution

Il n'y a pas de solution intégrée. La méthode la plus simple consiste à utiliser un tableau de char* où la valeur int de l'énum en indexe une chaîne contenant le nom descriptif de cette enum. Si vous avez un enum fragmenté (un qui ne commence pas à 0 ou a des trous dans la numérotation) où certaines des mappages int sont suffisamment élevés pour rendre un mappage basé sur un tableau impraticable, vous pouvez utiliser une table de hachage à la place.

Autres conseils

La technique de Faire quelque chose à la fois d'un identifiant C et d'une chaîne? peut être utilisé ici.

Comme d'habitude avec ce type de préprocesseur, l'écriture et la compréhension de la partie préprocesseur peuvent être difficiles. Cela inclut de passer des macros à d'autres macros et implique l'utilisation d'opérateurs # et ##, mais son utilisation est très facile. Je trouve ce style très utile pour les longs enums, où maintenir deux fois la même liste peut être très gênant.

Code usine - saisi une seule fois, généralement caché dans l'en-tête:

enumFactory.h:

// expansion macro for enum value definition
#define ENUM_VALUE(name,assign) name assign,

// expansion macro for enum to string conversion
#define ENUM_CASE(name,assign) case name: return #name;

// expansion macro for string to enum conversion
#define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name;

/// declare the access function and define enum values
#define DECLARE_ENUM(EnumType,ENUM_DEF) \
  enum EnumType { \
    ENUM_DEF(ENUM_VALUE) \
  }; \
  const char *GetString(EnumType dummy); \
  EnumType Get##EnumType##Value(const char *string); \

/// define the access function names
#define DEFINE_ENUM(EnumType,ENUM_DEF) \
  const char *GetString(EnumType value) \
  { \
    switch(value) \
    { \
      ENUM_DEF(ENUM_CASE) \
      default: return ""; /* handle input error */ \
    } \
  } \
  EnumType Get##EnumType##Value(const char *str) \
  { \
    ENUM_DEF(ENUM_STRCMP) \
    return (EnumType)0; /* handle input error */ \
  } \

Usine utilisée

someEnum.h:

#include "enumFactory.h"
#define SOME_ENUM(XX) \
    XX(FirstValue,) \
    XX(SecondValue,) \
    XX(SomeOtherValue,=50) \
    XX(OneMoreValue,=100) \

DECLARE_ENUM(SomeEnum,SOME_ENUM)

someEnum.cpp:

#include "someEnum.h"
DEFINE_ENUM(SomeEnum,SOME_ENUM)

La technique peut être facilement étendue de sorte que les macros XX acceptent plus d'arguments. Vous pouvez également avoir préparé plus de macros pour remplacer XX par différents besoins, semblables aux trois que j'ai fournis dans cet exemple.

Comparaison avec les macros X avec #include / #define / #undef

Bien que cela soit similaire aux X-Macros mentionnées par d’autres, je pense que cette solution est plus élégante en ce sens qu’elle ne nécessite aucune opération #dedefing, ce qui vous permet de cacher davantage de choses compliquées. le fichier d'en-tête est quelque chose que vous ne touchez pas du tout lorsque vous devez définir une nouvelle énumération. Par conséquent, la nouvelle définition d'enum est beaucoup plus courte et plus propre.

// Define your enumeration like this (in say numbers.h);
ENUM_BEGIN( Numbers )
    ENUM(ONE),
    ENUM(TWO),
    ENUM(FOUR)
ENUM_END( Numbers )

// The macros are defined in a more fundamental .h file (say defs.h);
#define ENUM_BEGIN(typ) enum typ {
#define ENUM(nam) nam
#define ENUM_END(typ) };

// Now in one and only one .c file, redefine the ENUM macros and reinclude
//  the numbers.h file to build a string table
#undef ENUM_BEGIN
#undef ENUM
#undef ENUM_END
#define ENUM_BEGIN(typ) const char * typ ## _name_table [] = {
#define ENUM(nam) #nam
#define ENUM_END(typ) };
#undef NUMBERS_H_INCLUDED   // whatever you need to do to enable reinclusion
#include "numbers.h"

// Now you can do exactly what you want to do, with no retyping, and for any
//  number of enumerated types defined with the ENUM macro family
//  Your code follows;
char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, Numbers_name_table[num]); // eg TWO -> "TWO"
    } break;
    default:
      return 0; //no match
  return 1;
}

// Sweet no ? After being frustrated by this for years, I finally came up
//  with this solution for my most recent project and plan to reuse the idea
//  forever

Il existe certainement un moyen de le faire: utilisez des macros X () . Ces macros utilisent le préprocesseur C pour construire des enums, des tableaux et des blocs de code à partir d'une liste de données source. Vous devez seulement ajouter de nouveaux éléments à la #define contenant la macro X (). L'instruction switch se développerait automatiquement.

Votre exemple peut être écrit comme suit:

 // Source data -- Enum, String
 #define X_NUMBERS \
    X(ONE,   "one") \
    X(TWO,   "two") \
    X(THREE, "three")

 ...

 // Use preprocessor to create the Enum
 typedef enum {
  #define X(Enum, String)       Enum,
   X_NUMBERS
  #undef X
 } Numbers;

 ...

 // Use Preprocessor to expand data into switch statement cases
 switch(num)
 {
 #define X(Enum, String) \
     case Enum:  strcpy(num_str, String); break;
 X_NUMBERS
 #undef X

     default: return 0; break;
 }
 return 1;

Il existe des méthodes plus efficaces (utiliser des macros X pour créer un tableau de chaînes et un index enum), mais il s’agit de la démonstration la plus simple.

Je sais que vous avez quelques bonnes réponses, mais connaissez-vous l'opérateur # dans le préprocesseur C?

Cela vous permet de faire ceci:

#define MACROSTR(k) #k

typedef enum {
    kZero,
    kOne,
    kTwo,
    kThree
} kConst;

static char *kConstStr[] = {
    MACROSTR(kZero),
    MACROSTR(kOne),
    MACROSTR(kTwo),
    MACROSTR(kThree)
};

static void kConstPrinter(kConst k)
{
    printf("%s", kConstStr[k]);
}

KISS. Vous ferez toutes sortes d'autres choses avec vos enums, alors pourquoi l'impression devrait-elle être différente? Oublier un cas dans votre routine d'impression n'est pas une grosse affaire si vous considérez qu'il y a environ 100 autres endroits où vous pouvez oublier un cas. Compilez simplement -Wall, qui vous avertira des correspondances de cas non exhaustives. N'utilisez pas & "; Défaut &"; parce que cela rendra le changement exhaustif et vous ne recevrez pas d’avertissements. Au lieu de cela, laissez le commutateur quitter et traiter le cas par défaut comme suit ...

const char *myenum_str(myenum e)
{
    switch(e) {
    case ONE: return "one";
    case TWO: return "two";
    }
    return "invalid";
}

C ou C ++ ne fournit pas cette fonctionnalité, même si j'en ai souvent eu besoin.

Le code suivant fonctionne, même s'il convient mieux aux énumérations non clairsemées.

typedef enum { ONE, TWO, THREE } Numbers;
char *strNumbers[] = {"one","two","three"};
printf ("Value for TWO is %s\n",strNumbers[TWO]);

Par non-clairsemé, je veux dire pas de la forme

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

puisque cela comporte d’énormes lacunes.

L’avantage de cette méthode est qu’elle met les définitions des énumérations et des chaînes près les unes des autres; avoir une instruction switch dans une fonction les spearates. Cela signifie que vous êtes moins susceptible de changer l'un sans l'autre.

Essayez de convertir les enums C ++ en chaînes . Les commentaires ont des améliorations qui résolvent le problème lorsque les éléments enum ont des valeurs arbitraires.

Utilisation de boost :: préprocesseur permet une solution élégante comme celle-ci:

Étape 1: incluez le fichier d'en-tête:

#include "EnumUtilities.h"

Étape 2: déclarez l'objet énumération avec la syntaxe suivante:

MakeEnum( TestData,
         (x)
         (y)
         (z)
         );

Étape 3: utilisez vos données:

Obtention du nombre d'éléments:

td::cout << "Number of Elements: " << TestDataCount << std::endl;

Obtention de la chaîne associée:

std::cout << "Value of " << TestData2String(x) << " is " << x << std::endl;
std::cout << "Value of " << TestData2String(y) << " is " << y << std::endl;
std::cout << "Value of " << TestData2String(z) << " is " << z << std::endl;

Obtention de la valeur enum de la chaîne associée:

std::cout << "Value of x is " << TestData2Enum("x") << std::endl;
std::cout << "Value of y is " << TestData2Enum("y") << std::endl;
std::cout << "Value of z is " << TestData2Enum("z") << std::endl;

Cela a l’air propre et compact, sans fichier supplémentaire à inclure. Le code que j'ai écrit dans EnumUtilities.h est le suivant:

#include <boost/preprocessor/seq/for_each.hpp>
#include <string>

#define REALLY_MAKE_STRING(x) #x
#define MAKE_STRING(x) REALLY_MAKE_STRING(x)
#define MACRO1(r, data, elem) elem,
#define MACRO1_STRING(r, data, elem)    case elem: return REALLY_MAKE_STRING(elem);
#define MACRO1_ENUM(r, data, elem)      if (REALLY_MAKE_STRING(elem) == eStrEl) return elem;


#define MakeEnum(eName, SEQ) \
    enum eName { BOOST_PP_SEQ_FOR_EACH(MACRO1, , SEQ) \
    last_##eName##_enum}; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_STRING, , SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    }; \
    static enum eName eName##2Enum(const std::string eStrEl) \
    { \
        BOOST_PP_SEQ_FOR_EACH(MACRO1_ENUM, , SEQ) \
        return (enum eName)0; \
    };

Il y a quelques limitations, c'est-à-dire celles de boost :: preprocessor. Dans ce cas, la liste des constantes ne peut pas dépasser 64 éléments.

En suivant la même logique, vous pouvez également penser à créer un enum clairsemé:

#define EnumName(Tuple)                 BOOST_PP_TUPLE_ELEM(2, 0, Tuple)
#define EnumValue(Tuple)                BOOST_PP_TUPLE_ELEM(2, 1, Tuple)
#define MACRO2(r, data, elem)           EnumName(elem) EnumValue(elem),
#define MACRO2_STRING(r, data, elem)    case EnumName(elem): return BOOST_PP_STRINGIZE(EnumName(elem));

#define MakeEnumEx(eName, SEQ) \
    enum eName { \
    BOOST_PP_SEQ_FOR_EACH(MACRO2, _, SEQ) \
    last_##eName##_enum }; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO2_STRING, _, SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    };  

Dans ce cas, la syntaxe est la suivante:

MakeEnumEx(TestEnum,
           ((x,))
           ((y,=1000))
           ((z,))
           );

L'utilisation est similaire à celle décrite ci-dessus (moins la fonction eName ## 2Enum, que vous pouvez essayer d'extrapoler à partir de la syntaxe précédente).

Je l'ai testé sur mac et linux, mais sachez que boost :: préprocesseur n'est peut-être pas entièrement portable.

En fusionnant certaines des techniques ici, j'ai trouvé la forme la plus simple:

#define MACROSTR(k) #k

#define X_NUMBERS \
       X(kZero  ) \
       X(kOne   ) \
       X(kTwo   ) \
       X(kThree ) \
       X(kFour  ) \
       X(kMax   )

enum {
#define X(Enum)       Enum,
    X_NUMBERS
#undef X
} kConst;

static char *kConstStr[] = {
#define X(String) MACROSTR(String),
    X_NUMBERS
#undef X
};

int main(void)
{
    int k;
    printf("Hello World!\n\n");

    for (k = 0; k < kMax; k++)
    {
        printf("%s\n", kConstStr[k]);
    }

    return 0;
}

Si vous utilisez gcc, il est possible d'utiliser:

const char * enum_to_string_map[]={ [enum1]='string1', [enum2]='string2'};

Ensuite, appelez par exemple

enum_to_string_map[enum1]

Découvrez les idées sur Mu Dynamics Research Labs - Archives du blog . J'ai trouvé ceci plus tôt cette année - j'ai oublié le contexte exact dans lequel je l'ai trouvé - et je l'ai adapté dans ce code. Nous pouvons débattre des avantages de l’ajout d’un E au début; il est applicable au problème spécifique abordé, mais ne fait pas partie d'une solution générale. Je l'ai caché dans mon dossier "vignettes" - où je garde des fragments de code intéressants au cas où je les voudrais plus tard. Je suis gêné de dire que je n'ai pas noté l'origine de cette idée à l'époque.

En-tête: paste1.h

/*
@(#)File:           $RCSfile: paste1.h,v $
@(#)Version:        $Revision: 1.1 $
@(#)Last changed:   $Date: 2008/05/17 21:38:05 $
@(#)Purpose:        Automated Token Pasting
*/

#ifndef JLSS_ID_PASTE_H
#define JLSS_ID_PASTE_H

/*
 * Common case when someone just includes this file.  In this case,
 * they just get the various E* tokens as good old enums.
 */
#if !defined(ETYPE)
#define ETYPE(val, desc) E##val,
#define ETYPE_ENUM
enum {
#endif /* ETYPE */

   ETYPE(PERM,  "Operation not permitted")
   ETYPE(NOENT, "No such file or directory")
   ETYPE(SRCH,  "No such process")
   ETYPE(INTR,  "Interrupted system call")
   ETYPE(IO,    "I/O error")
   ETYPE(NXIO,  "No such device or address")
   ETYPE(2BIG,  "Arg list too long")

/*
 * Close up the enum block in the common case of someone including
 * this file.
 */
#if defined(ETYPE_ENUM)
#undef ETYPE_ENUM
#undef ETYPE
ETYPE_MAX
};
#endif /* ETYPE_ENUM */

#endif /* JLSS_ID_PASTE_H */

Exemple de source:

/*
@(#)File:           $RCSfile: paste1.c,v $
@(#)Version:        $Revision: 1.2 $
@(#)Last changed:   $Date: 2008/06/24 01:03:38 $
@(#)Purpose:        Automated Token Pasting
*/

#include "paste1.h"

static const char *sys_errlist_internal[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) desc,
#include "paste1.h"
    0
#undef ETYPE
};

static const char *xerror(int err)
{
    if (err >= ETYPE_MAX || err <= 0)
        return "Unknown error";
    return sys_errlist_internal[err];
}

static const char*errlist_mnemonics[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) [E ## val] = "E" #val,
#include "paste1.h"
#undef ETYPE
};

#include <stdio.h>

int main(void)
{
    int i;

    for (i = 0; i < ETYPE_MAX; i++)
    {
        printf("%d: %-6s: %s\n", i, errlist_mnemonics[i], xerror(i));
    }
    return(0);
}

L’utilisation du pré-processeur C n’est pas nécessairement la plus propre au monde, mais elle empêche l’écriture multiple du contenu.

Si l'index enum est basé sur 0, vous pouvez placer les noms dans un tableau de char * et les indexer avec la valeur enum.

#define stringify( name ) # name

enum MyEnum {
    ENUMVAL1
};
...stuff...

stringify(EnumName::ENUMVAL1);  // Returns MyEnum::ENUMVAL1

Discussion supplémentaire sur cette méthode

astuces de directive de préprocesseur pour les nouveaux arrivants

J'ai créé une classe basée sur un modèle simple streamable_enum qui utilise les opérateurs de flux << et >> et qui est basée sur le std::map<Enum, std::string>:

.
#ifndef STREAMABLE_ENUM_HPP
#define STREAMABLE_ENUM_HPP

#include <iostream>
#include <string>
#include <map>

template <typename E>
class streamable_enum
{
public:
    typedef typename std::map<E, std::string> tostr_map_t;
    typedef typename std::map<std::string, E> fromstr_map_t;

    streamable_enum()
    {}

    streamable_enum(E val) :
        Val_(val)
    {}

    operator E() {
        return Val_;
    }

    bool operator==(const streamable_enum<E>& e) {
        return this->Val_ == e.Val_;
    }

    bool operator==(const E& e) {
        return this->Val_ == e;
    }

    static const tostr_map_t& to_string_map() {
        static tostr_map_t to_str_(get_enum_strings<E>());
        return to_str_;
    }

    static const fromstr_map_t& from_string_map() {
        static fromstr_map_t from_str_(reverse_map(to_string_map()));
        return from_str_;
    }
private:
    E Val_;

    static fromstr_map_t reverse_map(const tostr_map_t& eToS) {
        fromstr_map_t sToE;
        for (auto pr : eToS) {
            sToE.emplace(pr.second, pr.first);
        }
        return sToE;
    }
};

template <typename E>
streamable_enum<E> stream_enum(E e) {
    return streamable_enum<E>(e);
}

template <typename E>
typename streamable_enum<E>::tostr_map_t get_enum_strings() {
    // \todo throw an appropriate exception or display compile error/warning
    return {};
}

template <typename E>
std::ostream& operator<<(std::ostream& os, streamable_enum<E> e) {
    auto& mp = streamable_enum<E>::to_string_map();
    auto res = mp.find(e);
    if (res != mp.end()) {
        os << res->second;
    } else {
        os.setstate(std::ios_base::failbit);
    }
    return os;
}

template <typename E>
std::istream& operator>>(std::istream& is, streamable_enum<E>& e) {
    std::string str;
    is >> str;
    if (str.empty()) {
        is.setstate(std::ios_base::failbit);
    }
    auto& mp = streamable_enum<E>::from_string_map();
    auto res = mp.find(str);
    if (res != mp.end()) {
        e = res->second;
    } else {
        is.setstate(std::ios_base::failbit);
    }
    return is;
}

#endif

Utilisation:

#include "streamable_enum.hpp"

using std::cout;
using std::cin;
using std::endl;

enum Animal {
    CAT,
    DOG,
    TIGER,
    RABBIT
};

template <>
streamable_enum<Animal>::tostr_map_t get_enum_strings<Animal>() {
    return {
        { CAT, "Cat"},
        { DOG, "Dog" },
        { TIGER, "Tiger" },
        { RABBIT, "Rabbit" }
    };
}

int main(int argc, char* argv []) {
    cout << "What animal do you want to buy? Our offering:" << endl;
    for (auto pr : streamable_enum<Animal>::to_string_map()) {          // Use from_string_map() and pr.first instead
        cout << " " << pr.second << endl;                               // to have them sorted in alphabetical order
    }
    streamable_enum<Animal> anim;
    cin >> anim;
    if (!cin) {
        cout << "We don't have such animal here." << endl;
    } else if (anim == Animal::TIGER) {
        cout << stream_enum(Animal::TIGER) << " was a joke..." << endl;
    } else {
        cout << "Here you are!" << endl;
    }

    return 0;
}

Voici une solution utilisant des macros avec les fonctionnalités suivantes :

  1. n'écrivez chaque valeur de l'énumération qu'une seule fois, il n'y a donc pas de double liste à maintenir

  2. ne conservez pas les valeurs d'énumération dans un fichier séparé qui sera ensuite #inclus, afin que je puisse l'écrire où je veux

  3. ne remplacez pas l'énumération elle-même, je souhaite toujours que le type d'énumération soit défini, mais en plus, je veux pouvoir mapper chaque nom d'énumération à la chaîne correspondante (pour ne pas affecter le code existant)

  4. la recherche doit être rapide, donc de préférence sans switch-case, pour ces énormes énumérations

https://stackoverflow.com/a/20134475/1812866

Je pensais qu’une solution comme Boost.Fusion pour l’adaptation de structures et de classes serait bien, ils l’ont même eu à un moment donné, d’utiliser des énumérations comme séquence de fusion.

J'ai donc créé quelques petites macros pour générer le code permettant d’imprimer les enums. Ce n'est pas parfait et n'a rien à voir avec le code standard généré par Boost.Fusion, mais peut être utilisé comme les macros Boost Fusion. Je souhaite réellement générer les types nécessaires à l’intégration de Boost.Fusion dans cette infrastructure, ce qui permet d’imprimer les noms des membres de la structure, mais cela se produira plus tard. Pour l’instant, il ne s’agit que de macros:

#ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP
#define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP

#include <swissarmyknife/detail/config.hpp>

#include <string>
#include <ostream>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>


#define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C(                     \
    R, unused, ENUMERATION_ENTRY)                                               \
    case ENUMERATION_ENTRY:                                                     \
      return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY);                             \
    break;                                                                      

/**
 * \brief Adapts ENUM to reflectable types.
 *
 * \param ENUM_TYPE To be adapted
 * \param ENUMERATION_SEQ Sequence of enum states
 */
#define SWISSARMYKNIFE_ADAPT_ENUM(ENUM_TYPE, ENUMERATION_SEQ)                   \
    inline std::string to_string(const ENUM_TYPE& enum_value) {                 \
      switch (enum_value) {                                                     \
      BOOST_PP_SEQ_FOR_EACH(                                                    \
          SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C,                   \
          unused, ENUMERATION_SEQ)                                              \
        default:                                                                \
          return BOOST_PP_STRINGIZE(ENUM_TYPE);                                 \
      }                                                                         \
    }                                                                           \
                                                                                \
    inline std::ostream& operator<<(std::ostream& os, const ENUM_TYPE& value) { \
      os << to_string(value);                                                   \
      return os;                                                                \
    }

#endif

L'ancienne réponse ci-dessous est assez mauvaise, veuillez ne pas l'utiliser. :)

Ancienne réponse:

J'ai cherché un moyen de résoudre ce problème sans trop modifier la syntaxe de la déclaration enums. Je suis arrivé à une solution qui utilise le préprocesseur pour récupérer une chaîne à partir d'une déclaration enumified string.

Je peux définir des énumérations non clairsemées comme ceci:

SMART_ENUM(State, 
    enum State {
        RUNNING,
        SLEEPING, 
        FAULT, 
        UNKNOWN
    })

Et je peux interagir avec eux de différentes manières:

// With a stringstream
std::stringstream ss;
ss << State::FAULT;
std::string myEnumStr = ss.str();

//Directly to stdout
std::cout << State::FAULT << std::endl;

//to a string
std::string myStr = State::to_string(State::FAULT);

//from a string
State::State myEnumVal = State::from_string(State::FAULT);

Selon les définitions suivantes:

#define SMART_ENUM(enumTypeArg, ...)                                                     \
namespace enumTypeArg {                                                                  \
    __VA_ARGS__;                                                                         \
    std::ostream& operator<<(std::ostream& os, const enumTypeArg& val) {                 \
            os << swissarmyknife::enums::to_string(#__VA_ARGS__, val);                   \
            return os;                                                                   \
    }                                                                                    \
                                                                                     \
    std::string to_string(const enumTypeArg& val) {                                      \
            return swissarmyknife::enums::to_string(#__VA_ARGS__, val);                  \
    }                                                                                    \
                                                                                     \
    enumTypeArg from_string(const std::string &str) {                                    \
            return swissarmyknife::enums::from_string<enumTypeArg>(#__VA_ARGS__, str);   \
    }                                                                                    \
}                                                                                        \


namespace swissarmyknife { namespace enums {

    static inline std::string to_string(const std::string completeEnumDeclaration, size_t enumVal) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            if (enumVal == count) {
                std::string identifiersSubset = identifiers.substr(0, found);
                size_t beginId = identifiersSubset.find_last_of("{,");
                identifiersSubset = identifiersSubset.substr(beginId+1);
                boost::algorithm::trim(identifiersSubset);
                return identifiersSubset;
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("The enum declaration provided doesn't contains this state.");
    }                                                  

    template <typename EnumType>
    static inline EnumType from_string(const std::string completeEnumDeclaration, const std::string &enumStr) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            std::string identifiersSubset = identifiers.substr(0, found);
            size_t beginId = identifiersSubset.find_last_of("{,");
            identifiersSubset = identifiersSubset.substr(beginId+1);
            boost::algorithm::trim(identifiersSubset);

            if (identifiersSubset == enumStr) {
                return static_cast<EnumType>(count);
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("No valid enum value for the provided string");
    }                      

}}

Lorsque j'aurai besoin d'assistance pour une énumération parcimonieuse et plus longtemps, j'améliorerai les implémentations to_string et from_string avec boost :: xpressive, mais cela coûtera en temps de compilation en raison de l'importante modélisation effectuée et l'exécutable généré sera probablement plus volumineux. Mais cela a l’avantage qu’il sera plus lisible et plus facile à maintenir que ce code de manipulation manuelle très laid. : D

Sinon, j'ai toujours utilisé boost :: bimap pour effectuer de tels mappages entre la valeur enums et la chaîne, mais cela doit être maintenu manuellement.

Parce que je préfère ne pas utiliser de macros pour toutes les raisons habituelles, j’ai utilisé une solution macro plus limitée qui présente l’avantage de garder la macro de déclaration enum libre. Inconvénients: avoir à copier-coller la définition de macro pour chaque énumération et à ajouter explicitement une invocation de macro lors de l'ajout de valeurs à l'énum.

std::ostream& operator<<(std::ostream& os, provenance_wrapper::CaptureState cs)
{
#define HANDLE(x) case x: os << #x; break;
    switch (cs) {
    HANDLE(CaptureState::UNUSED)
    HANDLE(CaptureState::ACTIVE)
    HANDLE(CaptureState::CLOSED)
    }
    return os;
#undef HANDLE
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top