Wie man C ++ Enums einfach zu Strings abbildet

Ich habe eine Reihe von Enum-Typen in einigen Bibliothek Header-Dateien, die ich verwende, und ich möchte eine Möglichkeit haben, Enum-Werte in Benutzer-Strings zu konvertieren – und umgekehrt.

RTTI wird es nicht für mich tun, weil die ‘Benutzer-Strings’ etwas lesbarer sein müssen als die Enumerationen.

Eine Brute-Force-Lösung wäre eine Reihe von functionen wie diese, aber ich denke, das ist ein bisschen zu C-ähnlich.

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

Ich habe das Gefühl, dass es eine elegante Lösung mit Vorlagen gibt, aber ich kann mich noch nicht richtig damit befassen.

UPDATE: Danke für die Vorschläge – ich hätte klarstellen sollen, dass die Enums in einem Header einer Drittanbieter-Bibliothek definiert sind, so dass ich die Definition nicht ändern muss.

Mein Bauchgefühl ist jetzt, Vorlagen zu vermeiden und so etwas zu tun:

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

Solutions Collecting From Web of "Wie man C ++ Enums einfach zu Strings abbildet"

Wenn Sie möchten, dass sich die Enum-Namen selbst als Zeichenfolgen definieren, lesen Sie diesen Beitrag . Sonst std::map eine std::map gut. (Kein Punkt beim Kopieren von String-Literalen in std :: strings in der Map)

Für zusätzlichen syntaktischen Zucker, hier ist, wie man eine map_init class schreibt. Das Ziel ist es zu ermöglichen

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

Die functionsvorlage template map_init(T&) gibt einen map_init_helper . map_init_helper speichert ein T &, und definiert das triviale map_init_helper& operator()(typename T::key_type const&, typename T::value_type const&) . (Die Rückgabe *this von operator() ermöglicht die Verkettung von operator() , wie operator< < auf 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); } 

Da die function und die Hilfsklasse als Vorlagen dienen, können Sie sie für jede Map oder Map-ähnliche Struktur verwenden. Dh es kann auch Einträge zu std::unordered_map hinzufügen

Wenn Sie diese Helfer nicht gerne schreiben, bietet boost :: assign sofort die gleiche functionalität.

MSalters Lösung ist eine gute Lösung, implementiert aber boost::assign::map_list_of . Wenn Sie Boost haben, können Sie es direkt verwenden:

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

Automatisches Generieren eines Formulars aus einem anderen.

Quelle:

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

Generiert:

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

Wenn enum-Werte groß sind, könnte ein generiertes Formular unordered_map <> oder Vorlagen verwenden, wie von Constantin vorgeschlagen.

Quelle:

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

Generiert:

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

Beispiel:

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

Ich schlage vor, eine Mischung aus der Verwendung von X-Makros sind die beste Lösung und die folgenden Template-functionen:

Auszulesen marcinkoziukmyopenidcom und erweitert

 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) 

Farbe.def

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

Ich erinnere mich, dass ich dies an anderer Stelle auf StackOverflow beantwortet hatte. Wiederhole es hier. Im Grunde ist es eine Lösung, die auf variadischen Makros basiert und ziemlich einfach zu benutzen ist:

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

Um es in Ihrem Code zu verwenden, tun Sie einfach:

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

Wenn Sie String-Repräsentationen von MyEnum Variablen erhalten möchten, werden sie von Templates nicht abgeschnitten. Die Vorlage kann auf ganzzahlige Werte spezialisiert sein, die zur Kompilierungszeit bekannt sind.

Wenn es das ist, was du willst, versuche es mit:

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

Das ist zwar ausführlich, aber es werden Fehler wie die von Ihnen in Frage gestellt – Ihr case VAL1 ist doppelt vorhanden.

Ich benutze diese Lösung, die ich unten reproduziere:

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

Ich wäre versucht, eine Karte zu haben – und diese in das Enum einzubetten.

Setup mit m [MyEnum.VAL1] = “Wert 1”;

und alles ist fertig.

Ich habe diese functionalität mehrmals zum Debuggen / Analysieren von Code von anderen benötigt. Dafür habe ich ein Perl-Skript geschrieben, das eine class mit mehreren überladenen toString Methoden generiert. Jede toString Methode verwendet Enum als Argument und gibt const char* .

Natürlich analysiert das Skript C ++ nicht für Enums selbst, sondern verwendet Ctags zum Generieren der Symboltabelle.

Das Perl Skript ist hier: http://heinitz-it.de/download/enum2string/enum2string.pl.html

Ihre Antworten haben mich dazu inspiriert, einige Makros selbst zu schreiben. Meine Anforderungen waren die folgenden:

  1. Schreiben Sie jeden Wert der Enumeration nur einmal, so dass keine doppelten Listen verwaltet werden müssen

  2. behalte die Enum-Werte nicht in einer separaten Datei, die später # enthalten ist, damit ich sie schreiben kann, wo immer ich will

  3. Ersetze nicht die Enumeration selbst, ich möchte immer noch den enum-Typ definiert haben, aber zusätzlich möchte ich jeden enum-Namen der entsprechenden Zeichenkette zuordnen können (um Legacy-Code nicht zu beeinflussen)

  4. Die Suche sollte schnell sein, also vorzugsweise kein Switch-Case, für diese riesigen Enums

Dieser Code erstellt eine klassische Enumeration mit einigen Werten. Außerdem erstellt es als std :: map, die jeden enum-Wert seinem Namen zuordnet (zB map [E_SUNDAY] = “E_SUNDAY”, etc.)

Ok, hier ist der Code jetzt:

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 // Dies ist die Datei, die du einfügen willst, wenn du diese Sachen machen musst, du wirst die Makros daraus benutzen:

 #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 // Dies ist ein Beispiel für die Verwendung einer benutzerdefinierten Enumeration:

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

Prost.

Hier ist ein Versuch, < < und >> Stream-Operatoren auf enum automatisch mit einem einzeiligen Makro-Befehl nur zu bekommen …

Definitionen:

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

Verwendung:

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

Ich bin mir nicht sicher über die Grenzen dieses Schemas ... Kommentare sind willkommen!

Ich wollte nur diese mögliche elegante Lösung mit Makros zeigen. Das triggers das Problem nicht, aber ich denke, es ist ein guter Weg, das Problem zu überdenken.

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

---- BEARBEITEN ----

Nach einigen Recherchen im Internet und einigen eigenen Experimenten kam ich zu folgender Lösung:

 //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(colorinfo,COLOR_LIST)//colorinfo 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; } 

Ich wollte es nur posten, vielleicht könnte jemand diese Lösung nützlich finden. Es besteht keine Notwendigkeit für Templates-classn, keine Notwendigkeit von C ++ 11 und keine Notwendigkeit von Boost, so dass dies auch für einfache C verwendet werden könnte.

---- EDIT2 ----

Die Informationstabelle kann einige Probleme verursachen, wenn mehr als 2 Enums verwendet werden (Compiler-Problem). Die folgende Problemumgehung hat funktioniert:

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

Oben ist meine einfache Lösung. Ein Vorteil davon ist die “NUM”, die die Größe des Nachrichten-Arrays steuert, und es verhindert auch den Zugriff außerhalb der Grenzen (wenn Sie es mit Bedacht verwenden).

Sie können auch eine function definieren, um die Zeichenfolge abzurufen:

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

Zu meiner Lösung fand ich dann folgendes sehr interessant. Es triggerse im Allgemeinen das Synchronisationsproblem des obigen.

Folien hier: http://www.slideshare.net/arunksaaha/touchless-enum-tostring-28684724

Code hier: https://github.com/arunksaaha/enum_to_string

in der Kopfzeile:

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

in der .cpp-Datei:

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

Vorbehalt: Behandeln Sie keinen schlechten Array-Index. 🙂 Sie können jedoch leicht eine function hinzufügen, um die Enumeration zu überprüfen, bevor Sie die Zeichenfolge aus dem Array abrufen.

Ich hatte kürzlich das gleiche Problem mit einer Hersteller-Bibliothek (Fincad). Glücklicherweise hat der Anbieter XML-Dokumentation für alle Enums bereitgestellt. Ich habe am Ende eine Karte für jeden Aufzählungstyp erzeugt und eine Suchfunktion für jede Aufzählung bereitgestellt. Diese Technik ermöglicht es Ihnen auch, eine Suche außerhalb des Bereichs der Enumeration abzufangen.

Ich bin mir sicher, dass swig etwas ähnliches für dich tun könnte, aber ich freue mich, die Code-Generierungs-Utils zur Verfügung zu stellen, die in Ruby geschrieben sind.

Hier ist ein Beispiel des Codes:

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

Scheint so, als würdest du den anderen Weg gehen wollen (enum zu string, anstatt string to enum), aber das sollte trivial sein, umzukehren.

-Weiß

Sehen Sie, ob die folgende Syntax zu Ihnen passt:

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

Wenn dies der Fall ist, sollten Sie diesen Artikel lesen:
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 ist ein Attribut in GCC / LLVM, dann kannst du es so verwenden:

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