Come convertire una variabile di tipo enum in una stringa?

Come rendere printf per mostrare i valori delle variabili che sono di un tipo enum? Per esempio:

typedef enum {Linux, Apple, Windows} OS_type; OS_type myOS = Linux; 

e quello di cui ho bisogno è qualcosa di simile

 printenum(OS_type, "My OS is %s", myOS); 

che deve mostrare una stringa “Linux”, non un intero.

Suppongo, prima di tutto devo creare un array di stringhe indicizzato dal valore. Ma non so se questo è il modo più bello per farlo. È ansible a tutti?

Non c’è davvero un modo bellissimo per farlo. Basta impostare una serie di stringhe indicizzate dall’enumerazione.

Se si esegue molto output, è ansible definire un operatore << che accetta un parametro enum e effettua la ricerca.

La soluzione ingenua, ovviamente, è scrivere una funzione per ogni enumerazione che esegue la conversione in stringa:

 enum OS_type { Linux, Apple, Windows }; inline const char* ToString(OS_type v) { switch (v) { case Linux: return "Linux"; case Apple: return "Apple"; case Windows: return "Windows"; default: return "[Unknown OS_type]"; } } 

Questo, tuttavia, è un disastro di manutenzione. Con l’aiuto della libreria Boost.Preprocessor, che può essere utilizzata con entrambi i codici C e C ++, puoi facilmente sfruttare il preprocessore e lasciargli generare questa funzione. La macro di generazione è la seguente:

 #include  #define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem) \ case elem : return BOOST_PP_STRINGIZE(elem); #define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators) \ enum name { \ BOOST_PP_SEQ_ENUM(enumerators) \ }; \ \ inline const char* ToString(name v) \ { \ switch (v) \ { \ BOOST_PP_SEQ_FOR_EACH( \ X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE, \ name, \ enumerators \ ) \ default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]"; \ } \ } 

La prima macro (che inizia con X_ ) viene utilizzata internamente dal secondo. La seconda macro prima genera l’enumerazione, quindi genera una funzione ToString che accetta un object di quel tipo e restituisce il nome dell’enumeratore come una stringa (questa implementazione, per ovvi motivi, richiede che gli enumeratori eseguano l’associazione a valori univoci).

In C ++ è ansible implementare la funzione ToString come operator<< overload, ma penso che sia un po 'più pulito richiedere un esplicito " ToString " per convertire il valore in forma stringa.

Come esempio di utilizzo, l'enumerazione di tipo OS_type sarebbe definita come segue:

 DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows)) 

Mentre la macro sembra all'inizio come se ci fosse un sacco di lavoro, e la definizione di OS_type sembra piuttosto straniera, ricorda che devi scrivere una volta la macro, quindi puoi usarla per ogni enumerazione. È ansible aggiungere ulteriori funzionalità ad esso (ad esempio, una forma di stringa per la conversione enum) senza troppi problemi e risolve completamente il problema di manutenzione, poiché è necessario fornire i nomi una sola volta, quando si richiama la macro.

L'enumerazione può quindi essere utilizzata come se fosse definita normalmente:

 #include  int main() { OS_type t = Windows; std::cout << ToString(t) << " " << ToString(Apple) << std::endl; } 

I frammenti di codice in questo post, che iniziano con la riga #include , possono essere compilati come postati per dimostrare la soluzione.

Questa particolare soluzione è per C ++ poiché utilizza la syntax specifica di C ++ (ad esempio, nessun typedef enum ) e l'overloading delle funzioni, ma sarebbe semplice farlo funzionare anche con C.

Questo è il blocco del pre processore

 #ifndef GENERATE_ENUM_STRINGS #define DECL_ENUM_ELEMENT( element ) element #define BEGIN_ENUM( ENUM_NAME ) typedef enum tag##ENUM_NAME #define END_ENUM( ENUM_NAME ) ENUM_NAME; \ char* GetString##ENUM_NAME(enum tag##ENUM_NAME index); #else #define DECL_ENUM_ELEMENT( element ) #element #define BEGIN_ENUM( ENUM_NAME ) char* gs_##ENUM_NAME [] = #define END_ENUM( ENUM_NAME ) ; char* GetString##ENUM_NAME(enum \ tag##ENUM_NAME index){ return gs_##ENUM_NAME [index]; } #endif 

Definizione di enum

 BEGIN_ENUM(Os_type) { DECL_ENUM_ELEMENT(winblows), DECL_ENUM_ELEMENT(hackintosh), } 

Chiama usando

 GetStringOs_type(winblows); 

Preso da qui . Quant’è fico ? 🙂

Il problema con C enums è che non è un tipo proprio, come in C ++. Un enum in C è un modo per associare gli identificatori ai valori interi. Solo quello. Ecco perché un valore enum è intercambiabile con i valori interi.

Come indovini correttamente, un buon modo è creare una mapping tra il valore enum e una stringa. Per esempio:

 char * OS_type_label[] = { "Linux", "Apple", "Windows" }; 

Questo semplice esempio ha funzionato per me. Spero che questo ti aiuti.

 #include  #include  #define ENUM_TO_STR(ENUM) std::string(#ENUM) enum DIRECTION{NORTH, SOUTH, WEST, EAST}; int main() { std::cout << "Hello, " << ENUM_TO_STR(NORTH) << "!\n"; std::cout << "Hello, " << ENUM_TO_STR(SOUTH) << "!\n"; std::cout << "Hello, " << ENUM_TO_STR(EAST) << "!\n"; std::cout << "Hello, " << ENUM_TO_STR(WEST) << "!\n"; } 

Usa std::map e popolarlo con enum come chiave, e la rappresentazione della stringa come valori, quindi puoi fare questi:

 printf("My OS is %s", enumMap[myOS].c_str()); std::cout << enumMap[myOS] ; 

Ho combinato le soluzioni di James , Howard e Éder e ho creato un’implementazione più generica:

  • il valore int e la rappresentazione personalizzata della stringa possono essere facoltativamente definiti per ciascun elemento enum
  • “enum class” è usato

Il codice completo è scritto sotto (usare “DEFINE_ENUM_CLASS_WITH_ToString_METHOD” per definire un enum) ( demo online ).

 #include  #include  // ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ implementation is taken from: // http://lists.boost.org/boost-users/2012/09/76055.php // // This macro do the following: // input: // (Element1, "Element 1 string repr", 2) (Element2) (Element3, "Element 3 string repr") // output: // ((Element1, "Element 1 string repr", 2)) ((Element2)) ((Element3, "Element 3 string repr")) #define HELPER1(...) ((__VA_ARGS__)) HELPER2 #define HELPER2(...) ((__VA_ARGS__)) HELPER1 #define HELPER1_END #define HELPER2_END #define ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(sequence) BOOST_PP_CAT(HELPER1 sequence,_END) // CREATE_ENUM_ELEMENT_IMPL works in the following way: // if (elementTuple.GetSize() == 4) { // GENERATE: elementTuple.GetElement(0) = elementTuple.GetElement(2)), // } else { // GENERATE: elementTuple.GetElement(0), // } // Example 1: // CREATE_ENUM_ELEMENT_IMPL((Element1, "Element 1 string repr", 2, _)) // generates: // Element1 = 2, // // Example 2: // CREATE_ENUM_ELEMENT_IMPL((Element2, _)) // generates: // Element1, #define CREATE_ENUM_ELEMENT_IMPL(elementTuple) \ BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 4), \ BOOST_PP_TUPLE_ELEM(0, elementTuple) = BOOST_PP_TUPLE_ELEM(2, elementTuple), \ BOOST_PP_TUPLE_ELEM(0, elementTuple) \ ), // we have to add a dummy element at the end of a tuple in order to make // BOOST_PP_TUPLE_ELEM macro work in case an initial tuple has only one element. // if we have a tuple (Element1), BOOST_PP_TUPLE_ELEM(2, (Element1)) macro won't compile. // It requires that a tuple with only one element looked like (Element1,). // Unfortunately I couldn't find a way to make this transformation, so // I just use BOOST_PP_TUPLE_PUSH_BACK macro to add a dummy element at the end // of a tuple, in this case the initial tuple will look like (Element1, _) what // makes it compatible with BOOST_PP_TUPLE_ELEM macro #define CREATE_ENUM_ELEMENT(r, data, elementTuple) \ CREATE_ENUM_ELEMENT_IMPL(BOOST_PP_TUPLE_PUSH_BACK(elementTuple, _)) #define DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, element) \ case enumName::element : return BOOST_PP_STRINGIZE(element); #define DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, element, stringRepresentation) \ case enumName::element : return stringRepresentation; // GENERATE_CASE_FOR_SWITCH macro generates case for switch operator. // Algorithm of working is the following // if (elementTuple.GetSize() == 1) { // DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, elementTuple.GetElement(0)) // } else { // DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, elementTuple.GetElement(0), elementTuple.GetElement(1)) // } // // Example 1: // GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element1, "Element 1 string repr", 2)) // generates: // case EnumName::Element1 : return "Element 1 string repr"; // // Example 2: // GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element2)) // generates: // case EnumName::Element2 : return "Element2"; #define GENERATE_CASE_FOR_SWITCH(r, enumName, elementTuple) \ BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 1), \ DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple)), \ DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple), BOOST_PP_TUPLE_ELEM(1, elementTuple)) \ ) // DEFINE_ENUM_CLASS_WITH_ToString_METHOD final macro witch do the job #define DEFINE_ENUM_CLASS_WITH_ToString_METHOD(enumName, enumElements) \ enum class enumName { \ BOOST_PP_SEQ_FOR_EACH( \ CREATE_ENUM_ELEMENT, \ 0, \ ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements) \ ) \ }; \ inline const char* ToString(const enumName element) { \ switch (element) { \ BOOST_PP_SEQ_FOR_EACH( \ GENERATE_CASE_FOR_SWITCH, \ enumName, \ ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements) \ ) \ default: return "[Unknown " BOOST_PP_STRINGIZE(enumName) "]"; \ } \ } DEFINE_ENUM_CLASS_WITH_ToString_METHOD(Elements, (Element1) (Element2, "string representation for Element2 ") (Element3, "Element3 string representation", 1000) (Element4, "Element 4 string repr") (Element5, "Element5", 1005) (Element6, "Element6 ") (Element7) ) // Generates the following: // enum class Elements { // Element1, Element2, Element3 = 1000, Element4, Element5 = 1005, Element6, // }; // inline const char* ToString(const Elements element) { // switch (element) { // case Elements::Element1: return "Element1"; // case Elements::Element2: return "string representation for Element2 "; // case Elements::Element3: return "Element3 string representation"; // case Elements::Element4: return "Element 4 string repr"; // case Elements::Element5: return "Element5"; // case Elements::Element6: return "Element6 "; // case Elements::Element7: return "Element7"; // default: return "[Unknown " "Elements" "]"; // } // } int main() { std::cout << ToString(Elements::Element1) << std::endl; std::cout << ToString(Elements::Element2) << std::endl; std::cout << ToString(Elements::Element3) << std::endl; std::cout << ToString(Elements::Element4) << std::endl; std::cout << ToString(Elements::Element5) << std::endl; std::cout << ToString(Elements::Element6) << std::endl; std::cout << ToString(Elements::Element7) << std::endl; return 0; } 

Ci sono molte buone risposte qui, ma ho pensato che alcune persone potrebbero trovare il mio utile. Mi piace perché l’interfaccia che usi per definire la macro è tanto semplice quanto può ottenere. È anche utile perché non è necessario includere librerie aggiuntive: tutto viene fornito con C ++ e non richiede nemmeno una versione molto avanzata. Ho tirato pezzi da vari luoghi online, quindi non posso prendermi il merito di tutto questo, ma penso che sia abbastanza unico da giustificare una nuova risposta.

Per prima cosa create un file di intestazione … chiamatelo EnumMacros.h o qualcosa di simile e mettetelo dentro:

 // Search and remove whitespace from both ends of the string static std::string TrimEnumString(const std::string &s) { std::string::const_iterator it = s.begin(); while (it != s.end() && isspace(*it)) { it++; } std::string::const_reverse_iterator rit = s.rbegin(); while (rit.base() != it && isspace(*rit)) { rit++; } return std::string(it, rit.base()); } static void SplitEnumArgs(const char* szArgs, std::string Array[], int nMax) { std::stringstream ss(szArgs); std::string strSub; int nIdx = 0; while (ss.good() && (nIdx < nMax)) { getline(ss, strSub, ','); Array[nIdx] = TrimEnumString(strSub); nIdx++; } }; // This will to define an enum that is wrapped in a namespace of the same name along with ToString(), FromString(), and COUNT #define DECLARE_ENUM(ename, ...) \ namespace ename { \ enum ename { __VA_ARGS__, COUNT }; \ static std::string _Strings[COUNT]; \ static const char* ToString(ename e) { \ if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \ return _Strings[e].c_str(); \ } \ static ename FromString(const std::string& strEnum) { \ if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \ for (int i = 0; i < COUNT; i++) { if (_Strings[i] == strEnum) { return (ename)i; } } \ return COUNT; \ } \ } 

Quindi, nel tuo programma principale puoi farlo ...

 #include "EnumMacros.h" DECLARE_ENUM(OsType, Windows, Linux, Apple) void main() { OsType::OsType MyOs = OSType::Apple; printf("The value of '%s' is: %d of %d\n", OsType::ToString(MyOs), (int)OsType::FromString("Apple"), OsType::COUNT); } 

Dove l'output sarebbe >> Il valore di 'Apple' è: 2 di 4

Godere!

Supponendo che l’enumerazione sia già definita, puoi creare una serie di coppie:

 std::pair pairs [] = { std::pair(Linux, "Linux"), std::pair(Windows, "Windows"), std::pair(Apple, "Apple"), }; 

Ora puoi creare una mappa:

 std::map stdmap(pairs, pairs + sizeof(pairs) / sizeof(pairs[0])); 

Ora puoi usare la mappa. Se il tuo enum è cambiato, devi aggiungere / rimuovere coppie da coppie di array []. Penso che sia il modo più elegante per ottenere una stringa da enum in C ++.

Per C99 c’è P99_DECLARE_ENUM in P99 che ti permette semplicemente di dichiarare enum come questo:

 P99_DECLARE_ENUM(color, red, green, blue); 

e quindi usare color_getname(A) per ottenere una stringa con il nome del colore.

Hai provato questo:

 #define stringify( name ) # name enum enMyErrorValue { ERROR_INVALIDINPUT = 0, ERROR_NULLINPUT, ERROR_INPUTTOOMUCH, ERROR_IAMBUSY }; const char* enMyErrorValueNames[] = { stringify( ERROR_INVALIDINPUT ), stringify( ERROR_NULLINPUT ), stringify( ERROR_INPUTTOOMUCH ), stringify( ERROR_IAMBUSY ) }; void vPrintError( enMyErrorValue enError ) { cout << enMyErrorValueNames[ enError ] << endl; } int main() { vPrintError((enMyErrorValue)1); } 

La macro stringify() può essere utilizzata per trasformare qualsiasi testo nel codice in una stringa, ma solo il testo esatto tra parentesi. Non ci sono dereferenziazioni variabili o sostituzioni macro o altri tipi di cose fatte.

http://www.cplusplus.com/forum/general/2949/

Ecco il mio codice C ++:

 /* * File: main.cpp * Author: y2k1234 * * Created on June 14, 2013, 9:50 AM */ #include  #include  using namespace std; #define MESSAGE_LIST(OPERATOR) \ OPERATOR(MSG_A), \ OPERATOR(MSG_B), \ OPERATOR(MSG_C) #define GET_LIST_VALUE_OPERATOR(msg) ERROR_##msg##_VALUE #define GET_LIST_SRTING_OPERATOR(msg) "ERROR_"#msg"_NAME" enum ErrorMessagesEnum { MESSAGE_LIST(GET_LIST_VALUE_OPERATOR) }; static const char* ErrorMessagesName[] = { MESSAGE_LIST(GET_LIST_SRTING_OPERATOR) }; int main(int argc, char** argv) { int totalMessages = sizeof(ErrorMessagesName)/4; for (int i = 0; i < totalMessages; i++) { if (i == ERROR_MSG_A_VALUE) { printf ("ERROR_MSG_A_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]); } else if (i == ERROR_MSG_B_VALUE) { printf ("ERROR_MSG_B_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]); } else if (i == ERROR_MSG_C_VALUE) { printf ("ERROR_MSG_C_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]); } else { printf ("??? => [%d]=[%s]\n", i, ErrorMessagesName[i]); } } return 0; } Output: ERROR_MSG_A_VALUE => [0]=[ERROR_MSG_A_NAME] ERROR_MSG_B_VALUE => [1]=[ERROR_MSG_B_NAME] ERROR_MSG_C_VALUE => [2]=[ERROR_MSG_C_NAME] RUN SUCCESSFUL (total time: 126ms) 

La mia soluzione, non usando boost:

 #ifndef EN2STR_HXX_ #define EN2STR_HXX_ #define MAKE_STRING_1(str ) #str #define MAKE_STRING_2(str, ...) #str, MAKE_STRING_1(__VA_ARGS__) #define MAKE_STRING_3(str, ...) #str, MAKE_STRING_2(__VA_ARGS__) #define MAKE_STRING_4(str, ...) #str, MAKE_STRING_3(__VA_ARGS__) #define MAKE_STRING_5(str, ...) #str, MAKE_STRING_4(__VA_ARGS__) #define MAKE_STRING_6(str, ...) #str, MAKE_STRING_5(__VA_ARGS__) #define MAKE_STRING_7(str, ...) #str, MAKE_STRING_6(__VA_ARGS__) #define MAKE_STRING_8(str, ...) #str, MAKE_STRING_7(__VA_ARGS__) #define PRIMITIVE_CAT(a, b) a##b #define MAKE_STRING(N, ...) PRIMITIVE_CAT(MAKE_STRING_, N) (__VA_ARGS__) #define PP_RSEQ_N() 8,7,6,5,4,3,2,1,0 #define PP_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N #define PP_NARG_(...) PP_ARG_N(__VA_ARGS__) #define PP_NARG( ...) PP_NARG_(__VA_ARGS__,PP_RSEQ_N()) #define MAKE_ENUM(NAME, ...) enum NAME { __VA_ARGS__ }; \ struct NAME##_str { \ static const char * get(const NAME et) { \ static const char* NAME##Str[] = { \ MAKE_STRING(PP_NARG(__VA_ARGS__), __VA_ARGS__) }; \ return NAME##Str[et]; \ } \ }; #endif /* EN2STR_HXX_ */ 

Ed ecco come usarlo

 int main() { MAKE_ENUM(pippo, pp1, pp2, pp3,a,s,d); pippo c = d; cout << pippo_str::get(c) << "\n"; return 0; } 

Ecco il metodo Old Skool (usato per essere ampiamente utilizzato in gcc) usando solo il pre-processore C. Utile se stai generando strutture dati discrete ma devi mantenere l’ordine coerente tra loro. Le voci in mylist.tbl possono naturalmente essere estese a qualcosa di molto più complesso.

test.cpp:

 enum { #undef XX #define XX(name, ignore) name , #include "mylist.tbl" LAST_ENUM }; char * enum_names [] = { #undef XX #define XX(name, ignore) #name , #include "mylist.tbl" "LAST_ENUM" }; 

E poi mylist.tbl:

 /* A = enum */ /* B = some associated value */ /* AB */ XX( enum_1 , 100) XX( enum_2 , 100 ) XX( enum_3 , 200 ) XX( enum_4 , 900 ) XX( enum_5 , 500 ) 

Un po ‘tardi per la festa, ma ecco la mia soluzione C ++ 11:

 namespace std { template<> struct hash { std::size_t operator()(const enum_one & e) const { return static_cast(e); } }; template<> struct hash { //repeat for each enum type std::size_t operator()(const enum_two & e) const { return static_cast(e); } }; } const std::string & enum_name(const enum_one & e) { static const std::unordered_map names = { #define v_name(n) {enum_one::n, std::string(#n)} v_name(value1), v_name(value2), v_name(value3) #undef v_name }; return names.at(e); } const std::string & enum_name(const enum_two & e) { //repeat for each enum type ................. } 

La mia preferenza è di minimizzare sia la digitazione ripetitiva che le macro difficili da comprendere e di evitare l’introduzione di definizioni di macro nello spazio generale del compilatore.

Quindi, nel file di intestazione:

 enum Level{ /** * zero reserved for internal use */ verbose = 1, trace, debug, info, warn, fatal }; static Level readLevel(const char *); 

e l’implementazione di cpp è:

  Logger::Level Logger::readLevel(const char *in) { # define MATCH(x) if (strcmp(in,#x) ==0) return x; MATCH(verbose); MATCH(trace); MATCH(debug); MATCH(info); MATCH(warn); MATCH(fatal); # undef MATCH std::string s("No match for logging level "); s += in; throw new std::domain_error(s); } 

Nota il #undef della macro non appena abbiamo finito con esso.

Un altro in ritardo per la festa, usando il preprocessore:

  1 #define MY_ENUM_LIST \ 2 DEFINE_ENUM_ELEMENT(First) \ 3 DEFINE_ENUM_ELEMENT(Second) \ 4 DEFINE_ENUM_ELEMENT(Third) \ 5 6 //-------------------------------------- 7 #define DEFINE_ENUM_ELEMENT(name) , name 8 enum MyEnum { 9 Zeroth = 0 10 MY_ENUM_LIST 11 }; 12 #undef DEFINE_ENUM_ELEMENT 13 14 #define DEFINE_ENUM_ELEMENT(name) , #name 15 const char* MyEnumToString[] = { 16 "Zeroth" 17 MY_ENUM_LIST 18 }; 19 #undef DEFINE_ENUM_ELEMENT 20 21 #define DEFINE_ENUM_ELEMENT(name) else if (strcmp(s, #name)==0) return name; 22 enum MyEnum StringToMyEnum(const char* s){ 23 if (strcmp(s, "Zeroth")==0) return Zeroth; 24 MY_ENUM_LIST 25 return NULL; 26 } 27 #undef DEFINE_ENUM_ELEMENT 

(Ho appena inserito i numeri di riga in modo che sia più semplice parlare.) Le righe 1-4 sono ciò che modifichi per definire gli elementi dell’enumerazione. (Ho chiamato una “lista macro”, perché è una macro che crea una lista di cose. @Lundin mi informa che si tratta di una tecnica nota chiamata X-macros.)

La riga 7 definisce la macro interna in modo da compilare la dichiarazione enum effettiva nelle righe 8-11. La riga 12 non definisce la macro interna (solo per distriggersre l’avviso del compilatore).

La riga 14 definisce la macro interna in modo da creare una versione stringa del nome dell’enumerazione. Quindi le righe 15-18 generano un array che può convertire un valore enum nella stringa corrispondente.

Le righe 21-27 generano una funzione che converte una stringa nel valore enum o restituisce NULL se la stringa non corrisponde a nessuna.

Questo è un po ‘macchinoso nel modo in cui gestisce l’elemento 0th. In realtà ho lavorato su questo in passato.

Ammetto che questa tecnica disturba le persone che non vogliono pensare che il preprocessore stesso possa essere programmato per scrivere codice per te. Penso che illustri con forza la differenza tra leggibilità e manutenibilità . Il codice è difficile da leggere, ma se l’enumerazione ha qualche centinaio di elementi, puoi aggiungere, rimuovere o ridisporre gli elementi ed essere sicuro che il codice generato non abbia errori.

In c ++ come questo:

 enum OS_type{Linux, Apple, Windows}; std::string ToString( const OS_type v ) { const std::map< OS_type, std::string > lut = boost::assign::map_list_of( Linux, "Linux" )(Apple, "Apple )( Windows,"Windows"); std::map< OS_type, std::string >::const_iterator it = lut.find( v ); if ( lut.end() != it ) return it->second; return "NOT FOUND"; } 
 #include  

da http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C e dopo

 enum FORM { F_NONE = 0, F_BOX, F_CUBE, F_SPHERE, }; 

inserire

 Begin_Enum_String( FORM ) { Enum_String( F_NONE ); Enum_String( F_BOX ); Enum_String( F_CUBE ); Enum_String( F_SPHERE ); } End_Enum_String; 

Funziona bene se i valori nell’enumerazione non sono duplicati.

Esempio di codice per convertire un valore enum in stringa:

 enum FORM f = ... const std::string& str = EnumString< FORM >::From( f ); 

Esempio di codice per il contrario:

 assert( EnumString< FORM >::To( f, str ) ); 

Grazie James per il tuo suggerimento. È stato molto utile quindi ho implementato il contrario per contribuire in qualche modo.

 #include  #include  using namespace std; #define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem) \ case data::elem : return BOOST_PP_STRINGIZE(elem); #define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF(r, data, elem) \ if (BOOST_PP_SEQ_TAIL(data) == \ BOOST_PP_STRINGIZE(elem)) return \ static_cast(BOOST_PP_SEQ_HEAD(data)::elem); else #define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators) \ enum class name { \ BOOST_PP_SEQ_ENUM(enumerators) \ }; \ \ inline const char* ToString(name v) \ { \ switch (v) \ { \ BOOST_PP_SEQ_FOR_EACH( \ X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE, \ name, \ enumerators \ ) \ default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]"; \ } \ } \ \ inline int ToEnum(std::string s) \ { \ BOOST_PP_SEQ_FOR_EACH( \ X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF, \ (name)(s), \ enumerators \ ) \ return -1; \ } DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows)); int main(void) { OS_type t = OS_type::Windows; cout << ToString(t) << " " << ToString(OS_type::Apple) << " " << ToString(OS_type::Linux) << endl; cout << ToEnum("Windows") << " " << ToEnum("Apple") << " " << ToEnum("Linux") << endl; return 0; } 

Per estendere la risposta di James, qualcuno vuole un codice di esempio per supportare enum definire con un valore int, anch’io ho questo requisito, quindi ecco il mio modo:

La prima è la macro di uso interno, utilizzata da FOR_EACH:

 #define DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE(r, data, elem) \ BOOST_PP_IF( \ BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elem), 2), \ BOOST_PP_TUPLE_ELEM(0, elem) = BOOST_PP_TUPLE_ELEM(1, elem), \ BOOST_PP_TUPLE_ELEM(0, elem) ), 

E, ecco la macro di definizione:

 #define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators) \ enum name { \ BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE, \ 0, enumerators) }; 

Quindi, quando lo usi, potresti scrivere in questo modo:

 DEFINE_ENUM_WITH_STRING_CONVERSIONS(MyEnum, ((FIRST, 1)) ((SECOND)) ((MAX, SECOND)) ) 

che si espanderà a:

 enum MyEnum { FIRST = 1, SECOND, MAX = SECOND, }; 

L’idea di base è quella di definire un SEQ, che ogni elemento è una TUPLE, quindi possiamo aggiungere valore all’elemento enum. Nel ciclo FOR_EACH, seleziona l’elemento TUPLE size, se la dimensione è 2, espandi il codice in KEY = VALUE, altrimenti mantieni il primo elemento di TUPLE.

Poiché l’input SEQ è in realtà TUPLE, quindi se si desidera definire le funzioni STRINGIZE, potrebbe essere necessario pre-elaborare prima gli enumeratori di input, ecco la macro per eseguire il lavoro:

 #define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM(r, data, elem) \ BOOST_PP_TUPLE_ELEM(0, elem), #define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ(enumerators) \ BOOST_PP_SEQ_SUBSEQ( \ BOOST_PP_TUPLE_TO_SEQ( \ (BOOST_PP_SEQ_FOR_EACH( \ DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM, 0, enumerators) \ )), \ 0, \ BOOST_PP_SEQ_SIZE(enumerators)) 

La macro DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ manterrà il primo elemento in ogni TUPLE e successivamente convertirà in SEQ, ora modifica il codice di James, avrai la piena potenza.

La mia implementazione forse non è la più semplice, quindi se non trovi alcun codice pulito, il mio come riferimento.

Soluzione pulita e sicura in puro standard C:

 #include  #define STRF(x) #x #define STRINGIFY(x) STRF(x) /* list of enum constants */ #define TEST_0 hello #define TEST_1 world typedef enum { TEST_0, TEST_1, TEST_N } test_t; const char* test_str[]= { STRINGIFY(TEST_0), STRINGIFY(TEST_1), }; int main() { _Static_assert(sizeof test_str / sizeof *test_str == TEST_N, "Incorrect number of items in enum or look-up table"); printf("%d %s\n", hello, test_str[hello]); printf("%d %s\n", world, test_str[world]); test_t x = world; printf("%d %s\n", x, test_str[x]); return 0; } 

Produzione

 0 hello 1 world 1 world 

Fondamento logico

When solving the core problem “have enum constants with corresponding strings”, a sensible programmer will come up with the following requirements:

  • Avoid code repetition (“DRY” principle).
  • The code must be scalable, maintainable and safe even if items are added or removed inside the enum.
  • All code should be of high quality: easy to read, easy to maintain.

The first requirement, and maybe also the second, can be fulfilled with various messy macro solutions such as the infamous “x macro” trick, or other forms of macro magic. The problem with such solutions is that they leave you with a completely unreadable mess of mysterious macros – they don’t meet the third requirement above.

The only thing needed here is actually to have a string look-up table, which we can access by using the enum variable as index. Such a table must naturally correspond directly to the enum and vice versa. When one of them is updated, the other has to be updated too, or it will not work.


Explanation of the code

Suppose we have an enum like

 typedef enum { hello, world } test_t; 

This can be changed to

 #define TEST_0 hello #define TEST_1 world typedef enum { TEST_0, TEST_1, } test_t; 

With the advantage that these macro constants can now be used elsewhere, to for example generate a string look-up table. Converting a pre-processor constant to a string can be done with a “stringify” macro:

 #define STRF(x) #x #define STRINGIFY(x) STRF(x) const char* test_str[]= { STRINGIFY(TEST_0), STRINGIFY(TEST_1), }; 

E questo è tutto. By using hello , we get the enum constant with value 0. By using test_str[hello] we get the string “hello”.

To make the enum and look-up table correspond directly, we have to ensure that they contain the very same amount of items. If someone would maintain the code and only change the enum, and not the look-up table, or vice versa, this method won’t work.

The solution is to have the enum to tell you how many items it contains. There is a commonly-used C trick for this, simply add an item at the end, which only fills the purpose of telling how many items the enum has:

 typedef enum { TEST_0, TEST_1, TEST_N // will have value 2, there are 2 enum constants in this enum } test_t; 

Now we can check at compile time that the number of items in the enum is as many as the number of items in the look-up table, preferably with a C11 static assert:

 _Static_assert(sizeof test_str / sizeof *test_str == TEST_N, "Incorrect number of items in enum or look-up table"); 

(There are ugly but fully-functional ways to create static asserts in older versions of the C standard too, if someone insists on using dinosaur compilers. As for C++, it supports static asserts too.)


As a side note, in C11 we can also achieve higher type safety by changing the stringify macro:

 #define STRINGIFY(x) _Generic((x), int : STRF(x)) 

( int because enumeration constants are actually of type int , not test_t )

This will prevent code like STRINGIFY(random_stuff) from compiling.

What I made is a combination of what I have seen here and in similar questions on this site. I made this is Visual Studio 2013. I have not tested it with other compilers.

First of all I define a set of macros that will do the tricks.

 // concatenation macros #define CONCAT_(A, B) A ## B #define CONCAT(A, B) CONCAT_(A, B) // generic expansion and stringification macros #define EXPAND(X) X #define STRINGIFY(ARG) #ARG #define EXPANDSTRING(ARG) STRINGIFY(ARG) // number of arguments macros #define NUM_ARGS_(X100, X99, X98, X97, X96, X95, X94, X93, X92, X91, X90, X89, X88, X87, X86, X85, X84, X83, X82, X81, X80, X79, X78, X77, X76, X75, X74, X73, X72, X71, X70, X69, X68, X67, X66, X65, X64, X63, X62, X61, X60, X59, X58, X57, X56, X55, X54, X53, X52, X51, X50, X49, X48, X47, X46, X45, X44, X43, X42, X41, X40, X39, X38, X37, X36, X35, X34, X33, X32, X31, X30, X29, X28, X27, X26, X25, X24, X23, X22, X21, X20, X19, X18, X17, X16, X15, X14, X13, X12, X11, X10, X9, X8, X7, X6, X5, X4, X3, X2, X1, N, ...) N #define NUM_ARGS(...) EXPAND(NUM_ARGS_(__VA_ARGS__, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)) // argument extraction macros #define FIRST_ARG(ARG, ...) ARG #define REST_ARGS(ARG, ...) __VA_ARGS__ // arguments to strings macros #define ARGS_STR__(N, ...) ARGS_STR_##N(__VA_ARGS__) #define ARGS_STR_(N, ...) ARGS_STR__(N, __VA_ARGS__) #define ARGS_STR(...) ARGS_STR_(NUM_ARGS(__VA_ARGS__), __VA_ARGS__) #define ARGS_STR_1(ARG) EXPANDSTRING(ARG) #define ARGS_STR_2(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_1(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_3(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_2(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_4(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_3(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_5(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_4(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_6(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_5(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_7(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_6(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_8(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_7(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_9(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_8(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_10(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_9(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_11(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_10(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_12(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_11(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_13(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_12(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_14(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_13(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_15(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_14(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_16(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_15(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_17(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_16(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_18(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_17(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_19(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_18(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_20(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_19(EXPAND(REST_ARGS(__VA_ARGS__))) // expand until _100 or as much as you need 

Next define a single macro that will create the enum class and the functions to get the strings.

 #define ENUM(NAME, ...) \ enum class NAME \ { \ __VA_ARGS__ \ }; \ \ static const std::array CONCAT(NAME, Strings) = { ARGS_STR(__VA_ARGS__) }; \ \ inline const std::string& ToString(NAME value) \ { \ return CONCAT(NAME, Strings)[static_cast::type>(value)]; \ } \ \ inline std::ostream& operator<<(std::ostream& os, NAME value) \ { \ os << ToString(value); \ return os; \ } 

Now defining an enum type and have strings for it becomes really easy. Tutto quello che devi fare è:

 ENUM(MyEnumType, A, B, C); 

The following lines can be used to test it.

 int main() { std::cout << MyEnumTypeStrings.size() << std::endl; std::cout << ToString(MyEnumType::A) << std::endl; std::cout << ToString(MyEnumType::B) << std::endl; std::cout << ToString(MyEnumType::C) << std::endl; std::cout << MyEnumType::A << std::endl; std::cout << MyEnumType::B << std::endl; std::cout << MyEnumType::C << std::endl; auto myVar = MyEnumType::A; std::cout << myVar << std::endl; myVar = MyEnumType::B; std::cout << myVar << std::endl; myVar = MyEnumType::C; std::cout << myVar << std::endl; return 0; } 

Questo produrrà:

 3 A B C A B C A B C 

I believe it is very clean and easy to use. There are some limitations:

  • You cannot assign values to the enum members.
  • The enum member's values are used as index, but that should be fine, because everything is defined in a single macro.
  • You cannot use it to define an enum type inside a class.

If you can work around this. I think, especially how to use it, this is nice and lean. vantaggi:

  • Easy to use.
  • No string splitting at runtime required.
  • Separate strings are available at compile time.
  • Easy to read. The first set of macros may need an extra second, but aren't really that complicated.

A clean solution to this problem would be:

 #define RETURN_STR(val, e) {if (val == e) {return #e;}} std::string conv_dxgi_format_to_string(int value) { RETURN_STR(value, DXGI_FORMAT_UNKNOWN); RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_TYPELESS); RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_FLOAT); RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_UINT); RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_SINT); RETURN_STR(value, DXGI_FORMAT_R32G32B32_TYPELESS); RETURN_STR(value, DXGI_FORMAT_R32G32B32_FLOAT); /* ... */ return ""; } 

The good thing about this solution is that it is simple and also constructing the function can be done easily via copy and replace. Note that if you are going to do a lot of conversions and your enum has too many possible values, this solution might become CPU intensive.

I’m a bit late but here’s my solution using g++ and only standard libraries. I’ve tried to minimise namespace pollution and remove any need to re-typing enum names.

The header file “my_enum.hpp” is:

 #include  namespace ENUM_HELPERS{ int replace_commas_and_spaces_with_null(char* string){ int i, N; N = strlen(string); for(i=0; i 

Esempio di utilizzo:

 #include  #include "my_enum.hpp" MAKE_ENUM(Planets, MERCURY, VENUS, EARTH, MARS) int main(int argc, char** argv){ Planets::ENUM a_planet = Planets::EARTH; printf("%s\n", Planets::tostring(Planets::MERCURY)); printf("%s\n", Planets::tostring(a_planet)); } 

Questo produrrà:

 MERCURY EARTH 

You only have to define everything once, your namespace shouldn't be polluted, and all of the computation is only done once (the rest is just lookups). However, you don't get the type-safety of enum classs (they are still just short integers), you cannot assign values to the enums, you have to define enums somewhere you can define namespaces (eg globally).

I'm not sure how good the performance on this is, or if it's a good idea (I learnt C before C++ so my brain still works that way). If anyone knows why this is a bad idea feel free to point it out.

It’s 2017 but the question is still alive

Yet another way:

 #include  #define ERROR_VALUES \ ERROR_VALUE(NO_ERROR, 0, "OK") \ ERROR_VALUE(FILE_NOT_FOUND, 1, "Not found") \ ERROR_VALUE(LABEL_UNINITIALISED, 2, "Uninitialized usage") enum Error { #define ERROR_VALUE(NAME, VALUE, TEXT) NAME = VALUE, ERROR_VALUES #undef ERROR_VALUE }; inline std::ostream& operator<<(std::ostream& os, Error err) { int errVal = static_cast(err); switch (err) { #define ERROR_VALUE(NAME, VALUE, TEXT) case NAME: return os << "[" << errVal << "]" << #NAME << ", " << TEXT; ERROR_VALUES #undef ERROR_VALUE default: // If the error value isn't found (shouldn't happen) return os << errVal; } } int main() { std::cout << "Error: " << NO_ERROR << std::endl; std::cout << "Error: " << FILE_NOT_FOUND << std::endl; std::cout << "Error: " << LABEL_UNINITIALISED << std::endl; return 0; } 

Uscite:

 Error: [0]NO_ERROR, OK Error: [1]FILE_NOT_FOUND, Not found Error: [2]LABEL_UNINITIALISED, Uninitialized usage 
 #pragma once #include  #include  #include  #include  namespace StringifyEnum { static std::string TrimEnumString(const std::string &s) { std::string::const_iterator it = s.begin(); while (it != s.end() && isspace(*it)) { it++; } std::string::const_reverse_iterator rit = s.rbegin(); while (rit.base() != it && isspace(*rit)) { ++rit; } return std::string(it, rit.base()); } static std::vector SplitEnumArgs(const char* szArgs, int nMax) { std::vector enums; std::stringstream ss(szArgs); std::string strSub; int nIdx = 0; while (ss.good() && (nIdx < nMax)) { getline(ss, strSub, ','); enums.push_back(StringifyEnum::TrimEnumString(strSub)); ++nIdx; } return std::move(enums); } } #define DECLARE_ENUM_SEQ(ename, n, ...) \ enum class ename { __VA_ARGS__ }; \ const int MAX_NUMBER_OF_##ename(n); \ static std::vector ename##Strings = StringifyEnum::SplitEnumArgs(#__VA_ARGS__, MAX_NUMBER_OF_##ename); \ inline static std::string ename##ToString(ename e) { \ return ename##Strings.at((int)e); \ } \ inline static ename StringTo##ename(const std::string& en) { \ const auto it = std::find(ename##Strings.begin(), ename##Strings.end(), en); \ if (it != ename##Strings.end()) \ return (ename) std::distance(ename##Strings.begin(), it); \ throw std::runtime_error("Could not resolve string enum value"); \ } 

This is an elaborated class extended enum version…it does not add any other enum value other than those provided.

Usage: DECLARE_ENUM_SEQ(CameraMode, (3), Fly, FirstPerson, PerspectiveCorrect)

 enum class COLOR { RED, BLUE }; class Test { public: string getColorStr( COLOR ecolor ) { return _typeStr.find( ecolor )->second; } private: static map _typeStr; }; //于海洋map Test:: _typeStr = { { COLOR::RED, "red"}, { COLOR::BLUE, "blue"} }; int main() { Test t; std::cout << t.getColorStr( COLOR::BLUE ) << std::endl; }