Come mappare facilmente enunti c ++ alle stringhe

Ho un sacco di tipi di enum in alcuni file di intestazione della libreria che sto usando, e voglio avere un modo di convertire i valori enum in stringhe utente – e viceversa.

RTTI non lo farà per me, perché le “stringhe dell’utente” devono essere un po ‘più leggibili rispetto alle enumerazioni.

Una soluzione di forza bruta potrebbe essere una serie di funzioni come questa, ma ritengo che sia un po ‘troppo simile a C.

enum MyEnum {VAL1, VAL2,VAL3}; String getStringFromEnum(MyEnum e) { switch e { case VAL1: return "Value 1"; case VAL2: return "Value 2"; case VAL1: return "Value 3"; default: throw Exception("Bad MyEnum"); } } 

Ho la sensazione che ci sia una soluzione elegante con i modelli, ma non riesco ancora a capirlo.

AGGIORNAMENTO: Grazie per i suggerimenti: avrei dovuto chiarire che le enumerazioni sono definite in un’intestazione di libreria di terze parti, quindi non voglio dover cambiare la definizione di esse.

Il mio istinto ora è di evitare i template e fare qualcosa del genere:

 char * MyGetValue(int v, char *tmp); // implementation is trivial #define ENUM_MAP(type, strings) char * getStringValue(const type &T) \ { \ return MyGetValue((int)T, strings); \ } ; enum eee {AA,BB,CC}; - exists in library header file ; enum fff {DD,GG,HH}; ENUM_MAP(eee,"AA|BB|CC") ENUM_MAP(fff,"DD|GG|HH") // To use... eee e; fff f; std::cout<< getStringValue(e); std::cout<< getStringValue(f); 

Se vuoi che i nomi enum siano come stringhe, guarda questo post . Altrimenti, una std::map funzionerà bene. (Non importa copiare i tuoi valori letterali di stringa su std :: stringhe nella mappa)

Per lo zucchero sintattico extra, ecco come scrivere una class map_init. L’objective è consentire

 std::map MyMap; map_init(MyMap) (eValue1, "A") (eValue2, "B") (eValue3, "C") ; 

Il template map_init(T&) funzione template map_init(T&) restituisce un map_init_helper . map_init_helper memorizza un T &, e definisce il banale map_init_helper& operator()(typename T::key_type const&, typename T::value_type const&) . (Ritornare *this da operator() consente il concatenamento di operator() , come operator<< su std::ostream s)

 template struct map_init_helper { T& data; map_init_helper(T& d) : data(d) {} map_init_helper& operator() (typename T::key_type const& key, typename T::mapped_type const& value) { data[key] = value; return *this; } }; template map_init_helper map_init(T& item) { return map_init_helper(item); } 

Dal momento che le classi funzione e helper sono basate su modelli, puoi usarle per qualsiasi mappa o struttura simile a una mappa. Ad esempio, può anche aggiungere voci a std::unordered_map

Se non ti piace scrivere questi helper, boost :: assign offre la stessa funzionalità out of the box.

La soluzione di MSalters è buona, ma fondamentalmente re-implementa boost::assign::map_list_of . Se hai boost, puoi usarlo direttamente:

 #include  #include  #include  using boost::assign::map_list_of; enum eee { AA,BB,CC }; const boost::unordered_map eeeToString = map_list_of (AA, "AA") (BB, "BB") (CC, "CC"); int main() { std::cout << " enum AA = " << eeeToString.at(AA) << std::endl; return 0; } 

Genera automaticamente un modulo da un altro.

Fonte:

 enum { VALUE1, /* value 1 */ VALUE2, /* value 2 */ }; 

generated:

 const char* enum2str[] = { "value 1", /* VALUE1 */ "value 2", /* VALUE2 */ }; 

Se i valori enum sono grandi, un modulo generato potrebbe usare unordered_map <> o modelli come suggerito da Constantin.

Fonte:

 enum State{ state0 = 0, /* state 0 */ state1 = 1, /* state 1 */ state2 = 2, /* state 2 */ state3 = 4, /* state 3 */ state16 = 0x10000, /* state 16 */ }; 

generated:

 template  struct enum2str { static const char * const value; }; template  const char * const enum2str::value = "error"; template <> struct enum2str { static const char * const value; }; const char * const enum2str::value = "state 0"; 

Esempio:

 #include  int main() { std::cout << enum2str::value << std::endl; return 0; } 

Suggerisco un mix di usare X-macros sono la soluzione migliore e le seguenti funzioni di modello:

Prendere in prestito marcinkoziukmyopenidcom ed esteso

 enum Colours { # define X(a) a, # include "colours.def" # undef X ColoursCount }; char const* const colours_str[] = { # define X(a) #a, # include "colours.def" # undef X 0 }; template  T str2enum( const char* ); template  const char* enum2str( T ); #define STR2ENUM(TYPE,ARRAY) \ template <> \ TYPE str2enum( const char* str ) \ { \ for( int i = 0; i < (sizeof(ARRAY)/sizeof(ARRAY[0])); i++ ) \ if( !strcmp( ARRAY[i], str ) ) \ return TYPE(i); \ return TYPE(0); \ } #define ENUM2STR(TYPE,ARRAY) \ template <> \ const char* enum2str( TYPE v ) \ { \ return ARRAY[v]; \ } #define ENUMANDSTR(TYPE,ARRAY)\ STR2ENUM(TYPE,ARRAY) \ ENUM2STR(TYPE,ARRAY) ENUMANDSTR(Colours,colours_str) 

colour.def

 X(Red) X(Green) X(Blue) X(Cyan) X(Yellow) X(Magenta) 

Ricordo di averlo risposto altrove su StackOverflow. Ripetendolo qui. Fondamentalmente è una soluzione basata su macro variadic ed è piuttosto facile da usare:

 #define AWESOME_MAKE_ENUM(name, ...) enum class name { __VA_ARGS__, __COUNT}; \ inline std::ostream& operator<<(std::ostream& os, name value) { \ std::string enumName = #name; \ std::string str = #__VA_ARGS__; \ int len = str.length(); \ std::vector strings; \ std::ostringstream temp; \ for(int i = 0; i < len; i ++) { \ if(isspace(str[i])) continue; \ else if(str[i] == ',') { \ strings.push_back(temp.str()); \ temp.str(std::string());\ } \ else temp<< str[i]; \ } \ strings.push_back(temp.str()); \ os << enumName << "::" << strings[static_cast(value)]; \ return os;} 

Per usarlo nel tuo codice, fai semplicemente:

 AWESOME_MAKE_ENUM(Animal, DOG, CAT, HORSE ); auto dog = Animal::DOG; std::cout< 

Se si desidera ottenere rappresentazioni di stringa di variabili MyEnum , i modelli non lo taglieranno. Il modello può essere specializzato su valori integrali noti in fase di compilazione.

Tuttavia, se è quello che vuoi, prova:

 #include  enum MyEnum { VAL1, VAL2 }; template struct StrMyEnum { static char const* name() { return "Unknown"; } }; #define STRENUM(val, str) \ template<> struct StrMyEnum { \ static char const* name() { return str; }}; STRENUM(VAL1, "Value 1"); STRENUM(VAL2, "Value 2"); int main() { std::cout << StrMyEnum::name(); } 

Questo è prolisso, ma rileva errori come quello che hai fatto in questione: il tuo case VAL1 è duplicato.

Io uso questa soluzione che riproduco qui sotto:

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

Sarei tentato di avere una mappa m – e incorporare questo in enum.

installazione con m [MyEnum.VAL1] = “Valore 1”;

e tutto è fatto.

Ho richiesto questa funzionalità più volte per eseguire il debug / analisi del codice da altri. Per questo, ho scritto uno script Perl che genera una class con diversi metodi toString sovraccaricati. Ogni metodo toString accetta un Enum come argomento e restituisce const char* .

Ovviamente, lo script non analizza C ++ per le enumerazioni stesse, ma usa ctags per generare la tabella dei simboli.

Lo script Perl è qui: http://heinitz-it.de/download/enum2string/enum2string.pl.html

Le tue risposte mi hanno ispirato a scrivere alcune macro io stesso. I miei requisiti erano i seguenti:

  1. scrivi solo ogni valore dell’enum una volta, quindi non ci sono liste doppie da mantenere

  2. non mantenere i valori enum in un file separato che è in seguito #incluso, quindi posso scriverlo dove voglio

  3. non sostituire l’enum stesso, voglio comunque avere il tipo enum definito, ma oltre ad esso voglio essere in grado di mappare ogni nome enum nella stringa corrispondente (per non influenzare il codice legacy)

  4. la ricerca dovrebbe essere veloce, quindi preferibilmente senza interruttore, per quelle enumerazioni

Questo codice crea un enum classico con alcuni valori. Inoltre crea come std :: map che mappa ogni valore enum al suo nome (es. Map [E_SUNDAY] = “E_SUNDAY”, ecc.)

Ok, ecco il codice ora:

EnumUtilsImpl.h :

 map & operator , (map & dest, const pair & keyValue) { dest[keyValue.first] = keyValue.second; return dest; } #define ADD_TO_MAP(name, value) pair(name, #name) 

EnumUtils.h // questo è il file che vuoi includere ogni volta che devi fare questa roba, dovrai usare i macro da esso:

 #include "EnumUtilsImpl.h" #define ADD_TO_ENUM(name, value) \ name value #define MAKE_ENUM_MAP_GLOBAL(values, mapName) \ int __makeMap##mapName() {mapName, values(ADD_TO_MAP); return 0;} \ int __makeMapTmp##mapName = __makeMap##mapName(); #define MAKE_ENUM_MAP(values, mapName) \ mapName, values(ADD_TO_MAP); 

MyProjectCodeFile.h // questo è un esempio di come usarlo per creare un enum personalizzato:

 #include "EnumUtils.h* #define MyEnumValues(ADD) \ ADD(val1, ), \ ADD(val2, ), \ ADD(val3, = 100), \ ADD(val4, ) enum MyEnum { MyEnumValues(ADD_TO_ENUM) }; map MyEnumStrings; // this is how you initialize it outside any function MAKE_ENUM_MAP_GLOBAL(MyEnumValues, MyEnumStrings); void MyInitializationMethod() { // or you can initialize it inside one of your functions/methods MAKE_ENUM_MAP(MyEnumValues, MyEnumStrings); } 

Saluti.

Ecco un tentativo di ottenere << e >> operatori di streaming su enum automaticamente con un comando macro di una sola linea …

definizioni:

 #include  #include  #include  #include  #include  #include  #include  #define MAKE_STRING(str, ...) #str, MAKE_STRING1_(__VA_ARGS__) #define MAKE_STRING1_(str, ...) #str, MAKE_STRING2_(__VA_ARGS__) #define MAKE_STRING2_(str, ...) #str, MAKE_STRING3_(__VA_ARGS__) #define MAKE_STRING3_(str, ...) #str, MAKE_STRING4_(__VA_ARGS__) #define MAKE_STRING4_(str, ...) #str, MAKE_STRING5_(__VA_ARGS__) #define MAKE_STRING5_(str, ...) #str, MAKE_STRING6_(__VA_ARGS__) #define MAKE_STRING6_(str, ...) #str, MAKE_STRING7_(__VA_ARGS__) #define MAKE_STRING7_(str, ...) #str, MAKE_STRING8_(__VA_ARGS__) #define MAKE_STRING8_(str, ...) #str, MAKE_STRING9_(__VA_ARGS__) #define MAKE_STRING9_(str, ...) #str, MAKE_STRING10_(__VA_ARGS__) #define MAKE_STRING10_(str) #str #define MAKE_ENUM(name, ...) MAKE_ENUM_(, name, __VA_ARGS__) #define MAKE_CLASS_ENUM(name, ...) MAKE_ENUM_(friend, name, __VA_ARGS__) #define MAKE_ENUM_(attribute, name, ...) name { __VA_ARGS__ }; \ attribute std::istream& operator>>(std::istream& is, name& e) { \ const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \ std::string str; \ std::istream& r = is >> str; \ const size_t len = sizeof(name##Str)/sizeof(name##Str[0]); \ const std::vector enumStr(name##Str, name##Str + len); \ const std::vector::const_iterator it = std::find(enumStr.begin(), enumStr.end(), str); \ if (it != enumStr.end())\ e = name(it - enumStr.begin()); \ else \ throw std::runtime_error("Value \"" + str + "\" is not part of enum "#name); \ return r; \ }; \ attribute std::ostream& operator<<(std::ostream& os, const name& e) { \ const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \ return (os << name##Str[e]); \ } 

Uso:

 // Declare global enum enum MAKE_ENUM(Test3, Item13, Item23, Item33, Itdsdgem43); class Essai { public: // Declare enum inside class enum MAKE_CLASS_ENUM(Test, Item1, Item2, Item3, Itdsdgem4); }; int main() { std::cout << Essai::Item1 << std::endl; Essai::Test ddd = Essai::Item1; std::cout << ddd << std::endl; std::istringstream strm("Item2"); strm >> ddd; std::cout << (int) ddd << std::endl; std::cout << ddd << std::endl; } 

Non sono sicuro dei limiti di questo schema però ... i commenti sono ben accetti!

Volevo solo mostrare questa ansible soluzione elegante usando i macro. Questo non risolve il problema, ma penso che sia un buon modo per riflettere sul problema.

 #define MY_LIST(X) X(value1), X(value2), X(value3) enum eMyEnum { MY_LIST(PLAIN) }; const char *szMyEnum[] = { MY_LIST(STRINGY) }; int main(int argc, char *argv[]) { std::cout << szMyEnum[value1] << value1 <<" " << szMyEnum[value2] << value2 << std::endl; return 0; } 

---- MODIFICARE ----

Dopo alcune ricerche su internet e alcuni propri espedienti, sono arrivato alla seguente soluzione:

 //this is the enum definition #define COLOR_LIST(X) \ X( RED ,=21) \ X( GREEN ) \ X( BLUE ) \ X( PURPLE , =242) \ X( ORANGE ) \ X( YELLOW ) //these are the macros #define enumfunc(enums,value) enums, #define enumfunc2(enums,value) enums value, #define ENUM2SWITCHCASE(enums) case(enums): return #enums; #define AUTOENUM(enumname,listname) enum enumname{listname(enumfunc2)}; #define ENUM2STRTABLE(funname,listname) char* funname(int val) {switch(val) {listname(ENUM2SWITCHCASE) default: return "undef";}} #define ENUM2STRUCTINFO(spacename,listname) namespace spacename { int values[] = {listname(enumfunc)};int N = sizeof(values)/sizeof(int);ENUM2STRTABLE(enum2str,listname)}; //here the enum and the string enum map table are generated AUTOENUM(testenum,COLOR_LIST) ENUM2STRTABLE(testfunenum,COLOR_LIST) ENUM2STRUCTINFO(colorsnfo,COLOR_LIST)//colorsnfo structur {int values[]; int N; char * enum2str(int);} //debug macros #define str(a) #a #define xstr(a) str(a) int main( int argc, char** argv ) { testenum x = YELLOW; std::cout << testfunenum(GREEN) << " " << testfunenum(PURPLE) << PURPLE << " " << testfunenum(x); for (int i=0;i< colorinfo::N;i++) std::cout << std::endl << colorinfo::values[i] << " "<< colorinfo::enum2str(colorinfo::values[i]); return EXIT_SUCCESS; } 

Volevo solo pubblicarlo, forse qualcuno potrebbe trovare utile questa soluzione. Non è necessario che le classi di template non abbiano bisogno di c ++ 11 e non abbia bisogno di boost, quindi potrebbe anche essere usato per la semplice C.

---- EDIT2 ----

la tabella delle informazioni può produrre alcuni problemi quando si utilizzano più di 2 enumerazioni (problema del compilatore). La seguente soluzione ha funzionato:

 #define ENUM2STRUCTINFO(spacename,listname) namespace spacename { int spacename##_##values[] = {listname(enumfunc)};int spacename##_##N = sizeof(spacename##_##values)/sizeof(int);ENUM2STRTABLE(spacename##_##enum2str,listname)}; 
 typedef enum { ERR_CODE_OK = 0, ERR_CODE_SNAP, ERR_CODE_NUM } ERR_CODE; const char* g_err_msg[ERR_CODE_NUM] = { /* ERR_CODE_OK */ "OK", /* ERR_CODE_SNAP */ "Oh, snap!", }; 

Sopra è la mia soluzione semplice. Un vantaggio è il ‘NUM’ che controlla la dimensione della matrice di messaggi, impedisce anche l’accesso ai confini (se lo si usa con saggezza).

Puoi anche definire una funzione per ottenere la stringa:

 const char* get_err_msg(ERR_CODE code) { return g_err_msg[code]; } 

Oltre alla mia soluzione, ho trovato il seguente piuttosto interessante. Solitamente ha risolto il problema di sincronizzazione di quello precedente.

Diapositive qui: http://www.slideshare.net/arunksaha/touchless-enum-tostring-28684724

Codice qui: https://github.com/arunksaha/enum_to_string

nell’intestazione:

 enum EFooOptions { FooOptionsA = 0, EFooOptionsMin = 0, FooOptionsB, FooOptionsC, FooOptionsD EFooOptionsMax }; extern const wchar* FOO_OPTIONS[EFooOptionsMax]; 

nel file .cpp:

 const wchar* FOO_OPTIONS[] = { L"One", L"Two", L"Three", L"Four" }; 

Avvertenza: non gestire l’indice di array errato. 🙂 Ma puoi facilmente aggiungere una funzione per verificare l’enumerazione prima di ottenere la stringa dall’array.

Recentemente ho avuto lo stesso problema con una libreria di venditori (Fincad). Fortunatamente, il fornitore ha fornito la documentazione XML per tutte le enumerazioni. Ho finito per generare una mappa per ogni tipo di enum e fornire una funzione di ricerca per ogni enum. Questa tecnica ti consente anche di intercettare una ricerca al di fuori della portata dell’enumerazione.

Sono sicuro che Swig potrebbe fare qualcosa di simile per te, ma sono felice di fornire gli utils di generazione del codice scritti in ruby.

Ecco un esempio del codice:

 std::map init_FCSW2_map() { std::map ans; ans["Act365Fixed"] = FCSW2::Act365Fixed; ans["actual/365 (fixed)"] = FCSW2::Act365Fixed; ans["Act360"] = FCSW2::Act360; ans["actual/360"] = FCSW2::Act360; ans["Act365Act"] = FCSW2::Act365Act; ans["actual/365 (actual)"] = FCSW2::Act365Act; ans["ISDA30360"] = FCSW2::ISDA30360; ans["30/360 (ISDA)"] = FCSW2::ISDA30360; ans["ISMA30E360"] = FCSW2::ISMA30E360; ans["30E/360 (30/360 ISMA)"] = FCSW2::ISMA30E360; return ans; } switches::FCSW2::type FCSW2_lookup(const char* fincad_switch) { static std::map switch_map = init_FCSW2_map(); std::map::iterator it = switch_map.find(fincad_switch); if(it != switch_map.end()) { return it->second; } else { throw FCSwitchLookupError("Bad Match: FCSW2"); } } 

Sembra che tu voglia andare dall’altra parte (enum a stringa, piuttosto che stringa a enum), ma questo dovrebbe essere banale da invertire.

-Whit

Verifica se la seguente syntax ti soddisfa:

 // WeekEnd enumeration enum WeekEnd { Sunday = 1, Saturday = 7 }; // String support for WeekEnd Begin_Enum_String( WeekEnd ) { Enum_String( Sunday ); Enum_String( Saturday ); } End_Enum_String; // Convert from WeekEnd to string const std::string &str = EnumString::From( Saturday ); // str should now be "Saturday" // Convert from string to WeekEnd WeekEnd w; EnumString::To( w, "Sunday" ); // w should now be Sunday 

Se lo fa, allora potresti voler dare un’occhiata a questo articolo:
http://www.gamedev.net/reference/snippets/features/cppstringizing/

 enum MyEnum { VAL1, VAL2,VAL3 }; #define StringMyEnum(e) ({__unused MyEnum _e = e;std::string(#e);}) #define CStringMyEnum(e) ({__unused MyEnum _e = e;#e;}) 

__unused è un attributo in GCC / LLVM, quindi puoi usare in questo modo:

 std::string s = StringMyEnum(VAL1); const char *c = CStringMyEnum(VAL1);