Domanda

Ecco cosa sto cercando di fare:

typedef enum { ONE, TWO, THREE } Numbers;

Sto cercando di scrivere una funzione che vorresti fare un interruttore di caso analogo al seguente:

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

Invece di definire in ogni caso, c'è un modo per impostare utilizzando la variabile enum come sto cercando di fare qui sopra?

È stato utile?

Soluzione

Non esiste una soluzione integrata. Il modo più semplice è con una matrice di char* in cui il valore int dell'enum si indicizza su una stringa contenente il nome descrittivo di quell'enum. Se hai un enum sparso (uno che non inizia da 0 o ha delle lacune nella numerazione) in cui alcuni dei int mapping sono abbastanza alti da rendere impraticabile un mapping basato su array, allora puoi usare una tabella hash invece.

Altri suggerimenti

La tecnica di Stai realizzando qualcosa come un identificatore C e una stringa? può essere utilizzato qui.

Come al solito con tali elementi di preprocessore, scrivere e comprendere la parte del preprocessore può essere difficile, e include il passaggio di macro ad altre macro e comporta l'utilizzo di operatori # e ##, ma l'utilizzo è molto semplice. Trovo questo stile molto utile per le enumerazioni lunghe, dove mantenere lo stesso elenco due volte può essere davvero problematico.

Codice di fabbrica: digitato una sola volta, generalmente nascosto nell'intestazione:

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 */ \
  } \

Utilizzato in fabbrica

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 tecnica può essere facilmente estesa in modo che le macro XX accettino più argomenti e puoi anche aver preparato più macro per sostituire XX per esigenze diverse, simili alle tre che ho fornito in questo esempio.

Confronto con X-Macro usando #include / #define / #undef

Anche se questo è simile agli X-Macro citati da altri, penso che questa soluzione sia più elegante in quanto non richiede #undefing nulla, il che ti permette di nascondere di più le cose complicate è in fabbrica il file di intestazione - il file di intestazione è qualcosa che non tocchi affatto quando devi definire un nuovo enum, quindi la nuova definizione di enum è molto più breve e più pulita.

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

Esiste sicuramente un modo per farlo: utilizzare Macro X () . Queste macro utilizzano il preprocessore C per costruire enum, array e blocchi di codice da un elenco di dati di origine. Devi solo aggiungere nuovi elementi a #define contenente la macro X (). L'istruzione switch si espanderebbe automaticamente.

Il tuo esempio può essere scritto come segue:

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

Ci sono modi più efficienti (cioè usando X Macro per creare un array di stringhe e un indice enum), ma questa è la demo più semplice.

So che hai un paio di buone risposte solide, ma conosci l'operatore # nel preprocessore C?

Ti consente di farlo:

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

BACIO. Farai ogni sorta di altre cose switch / case con i tuoi enum quindi quindi perché la stampa dovrebbe essere diversa? Dimenticare un caso nella routine di stampa non è un grosso problema se si considera che ci sono circa 100 altri posti in cui è possibile dimenticare un caso. Basta compilare -Wall, che avviserà di casi non esaustivi. Non usare & Quot; default & Quot; perché ciò renderà l'interruttore esaustivo e non riceverai avvisi. Invece, lascia che l'interruttore esca e gestisca il caso predefinito in questo modo ...

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

C o C ++ non fornisce questa funzionalità, anche se ne ho avuto spesso bisogno.

Il seguente codice funziona, sebbene sia più adatto per enumerazioni non sparse.

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

Per non sparse, intendo non del modulo

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

dal momento che ha enormi lacune in esso.

Il vantaggio di questo metodo è che avvicina le definizioni degli enum e delle stringhe; avere un'istruzione switch in una funzione li spear. Ciò significa che hai meno probabilità di cambiarne uno senza l'altro.

Prova Conversione di enumerazioni C ++ in stringhe . I commenti presentano miglioramenti che risolvono il problema quando gli oggetti enum hanno valori arbitrari.

L'uso di boost :: preprocessor rende possibile una soluzione elegante come la seguente:

Passaggio 1: includere il file di intestazione:

#include "EnumUtilities.h"

Passaggio 2: dichiarare l'oggetto di enumerazione con la sintassi seguente:

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

Passaggio 3: utilizza i tuoi dati:

Ottenere il numero di elementi:

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

Ottenere la stringa associata:

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;

Ottenere il valore enum dalla stringa associata:

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;

Sembra pulito e compatto, senza file extra da includere. Il codice che ho scritto in EnumUtilities.h è il seguente:

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

Vi sono alcune limitazioni, ad esempio quelle di boost :: preprocessore. In questo caso, l'elenco delle costanti non può essere più grande di 64 elementi.

Seguendo la stessa logica, potresti anche pensare di creare enum sparsi:

#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."; \
        }; \
    };  

In questo caso, la sintassi è:

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

L'utilizzo è simile al precedente (meno la funzione eName ## 2Enum, che potresti provare a estrapolare dalla sintassi precedente).

L'ho provato su Mac e Linux, ma tieni presente che boost :: preprocessor potrebbe non essere completamente portatile.

Unendo alcune delle tecniche qui ho trovato la forma più semplice:

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

Se stai usando gcc, è possibile usare:

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

Quindi chiama per esempio

enum_to_string_map[enum1]

Scopri le idee su Mu Dynamics Research Labs - Blog Archive . L'ho trovato all'inizio di quest'anno - ho dimenticato il contesto esatto in cui l'ho trovato - e l'ho adattato a questo codice. Possiamo discutere il merito di aggiungere una E in primo piano; è applicabile al problema specifico affrontato, ma non fa parte di una soluzione generale. L'ho nascosto nella mia cartella 'vignette' - dove tengo interessanti frammenti di codice nel caso li volessi più tardi. Sono imbarazzato nel dire che non ho preso nota di dove è nata questa idea all'epoca.

Intestazione: incolla1.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 */

Fonte di esempio:

/*
@(#)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);
}

Non necessariamente l'uso più pulito del mondo del pre-processore C, ma impedisce di scrivere il materiale più volte.

Se l'indice enum è basato su 0, è possibile inserire i nomi in un array di caratteri * e indicizzarli con il valore enum.

#define stringify( name ) # name

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

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

Ulteriore discussione su questo metodo

La direttiva del preprocessore trucchi per i nuovi arrivati

Ho creato una semplice classe modello streamable_enum che utilizza gli operatori di flusso << e >> e si basa su 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

Utilizzo:

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

Ecco una soluzione che utilizza le macro con le seguenti funzionalità:

  1. scrive ogni valore dell'enum una sola volta, quindi non ci sono doppi elenchi da mantenere

  2. non conserva i valori enum in un file separato che verrà successivamente #incluso, quindi posso scriverlo dove voglio

  3. non sostituisce l'enum stesso, voglio ancora che sia definito il tipo di enum, ma in aggiunta ad esso voglio essere in grado di mappare ogni nome di enum sulla stringa corrispondente (per non influenzare il codice legacy)

  4. la ricerca dovrebbe essere veloce, quindi preferibilmente nessuna custodia, per quegli enormi enum

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

Ho pensato che una soluzione come Boost.Fusion per adattare strutture e classi sarebbe stata gradevole, a un certo punto ce l'hanno persino fatta, per usare gli enum come sequenza di fusione.

Quindi ho creato solo alcune piccole macro per generare il codice per stampare le enumerazioni. Questo non è perfetto e non ha nulla da vedere con Boost.Fusion generato codice boilerplate, ma può essere utilizzato come le macro Boost Fusion. Voglio davvero generare i tipi necessari a Boost.Fusion per integrarsi in questa infrastruttura che consente di stampare i nomi dei membri della struttura, ma ciò accadrà in seguito, per ora si tratta solo di macro:

#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

La vecchia risposta di seguito è piuttosto negativa, per favore non usarla. :)

Vecchia risposta:

Ho cercato un modo per risolvere questo problema senza cambiare troppo la sintassi della dichiarazione enums. Sono arrivato a una soluzione che utilizza il preprocessore per recuperare una stringa da una dichiarazione enum rigorosa.

Sono in grado di definire enumerazioni non sparse come questa:

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

E posso interagire con loro in diversi modi:

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

Basato sulle seguenti definizioni:

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

}}

Quando avrò bisogno di supporto per enum sparsi e quando avrò più tempo, migliorerò le implementazioni to_string e from_string con boost :: xpressive, ma questo costerà in tempo di compilazione a causa dell'importante templating eseguito e il file eseguibile generato sarà probabilmente più grande. Ma questo ha il vantaggio che sarà più leggibile e mantenibile rispetto a questo brutto codice di manipolazione manuale delle stringhe. : D

Altrimenti ho sempre usato boost :: bimap per eseguire tali mappature tra valore di enum e stringa, ma deve essere mantenuto manualmente.

Poiché preferisco non usare le macro per tutti i soliti motivi, ho usato una soluzione macro più limitata che ha il vantaggio di mantenere libera la macro di dichiarazione enum. Gli svantaggi includono la necessità di copiare e incollare la definizione macro per ogni enum e di aggiungere esplicitamente una chiamata macro quando si aggiungono valori all'enum.

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
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top