web-dev-qa-db-fra.com

Un moyen facile d'utiliser des variables de types enum comme chaîne en C?

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 fois, y a-t-il un moyen de le définir en utilisant la variable enum comme je le fais plus haut?

83
zxcv

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 est indexée dans une chaîne contenant le nom descriptif de cette enum. Si vous avez une enum peu dense (une qui ne commence pas à 0 ou a des trous dans la numérotation) où certains des mappages int sont suffisamment élevés pour rendre un mappage basé sur un tableau impossible, vous pouvez utiliser une table de hachage.

13
sk.

La technique de Faire de quelque chose à la fois un identifiant C et 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 implique 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 d'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 manière à ce 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 #undefing, ce qui vous permet de masquer davantage de choses compliquées. 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’énumération est beaucoup plus courte et plus propre.

61
Suma
// 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
61
Bill Forster

Il existe certainement un moyen de le faire - utilisez les macros X() . Ces macros utilisent le préprocesseur C pour construire des énums, des tableaux et des blocs de code à partir d'une liste de données source. Vous devez uniquement 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 moyens 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.

13
JayG

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]);
}
8
plinth

BAISER. Vous ferez toutes sortes d'autres choses avec vos enums, alors pourquoi est-ce que l'impression devrait être différente? Oublier un cas dans votre routine d'impression n'est pas une grosse affaire quand 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 "default" car cela rendra le commutateur exhaustif et vous ne recevrez pas d'avertissements. Au lieu de cela, laissez le commutateur quitter et traiter le cas par défaut comme si ...

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

C ou C++ ne fournit pas cette fonctionnalité, bien que 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é, j'entends pas de la forme

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

car cela a 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.

5
paxdiablo

Essayez Conversion des 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.

4
Bob Nadler

L'utilisation de boost :: preprocessor 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 d'énumération avec la syntaxe suivante:

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

Étape 3: utilisez vos données:

Obtenir le nombre d'éléments:

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

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

Obtenir 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 semble propre et compact, sans fichiers supplémentaires à 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 pourriez aussi 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 totalement portable.

4
Giacomo M.

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

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

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

Ensuite, il suffit d'appeler par exemple

enum_to_string_map[enum1]
2
janisj

Découvrez les idées sur Laboratoires de recherche Mu Dynamics - Blog Archive . J'ai trouvé cela 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 conserve 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 d’écrire le contenu plusieurs fois.

1
Jonathan Leffler
1
OJW

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

0
Colen

J'ai créé une classe basée sur un modèle streamable_enum qui utilise les opérateurs de flux << et >> et 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

Usage:

#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;
}
0
Robert Husák

Parce que je préfère ne pas utiliser de macros pour toutes les raisons habituelles, j'ai utilisé une solution de 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 un appel de macro lors de l'ajout de valeurs à l'énumération.

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
}
0
gerardw

Je pensais qu'une solution comme Boost.Fusion pour adapter les structures et les classes serait bien, ils l'ont même eue à un moment donné, d'utiliser énums 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 veux vraiment générer les types nécessaires à Boost.Fusion pour s'intégrer 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, merci de 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 enum stringified.

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

Basé sur 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");
    }                      

}}

Quand j'aurai besoin de support pour sparse enum et que j'aurai plus de temps, j'améliorerai les implémentations to_string et from_string avec boost :: xpressive, mais cela coûtera en temps de compilation à cause du templating important effectué et l'exécutable généré est susceptible d'être vraiment plus grand. Mais cela a l’avantage qu’il sera plus lisible et maintenable que ce code de manipulation manuelle très laid. :RÉ

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

0
daminetreg

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

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

  2. ne conservez pas les valeurs enum dans un fichier séparé, #included ultérieurement, pour que je puisse l'écrire où je veux

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

  4. la recherche devrait être rapide, donc de préférence pas de commutateur, pour ces énormes énormes

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

0
muqker
#define stringify( name ) # name

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

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

Suite de la discussion sur cette méthode

Astuces de directive du préprocesseur pour les nouveaux venus

0
Ben