Cómo mapear fácilmente enums de C ++ a cadenas

Tengo un montón de tipos enum en algunos archivos de encabezado de biblioteca que estoy usando, y quiero tener una forma de convertir valores enum a cadenas de usuario, y viceversa.

RTTI no lo hará por mí, porque las ‘cadenas de usuario’ deben ser un poco más legibles que las enumeraciones.

Una solución de fuerza bruta sería un conjunto de funciones como esta, pero creo que es un poco demasiado parecido 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"); } } 

Tengo la intuición de que hay una solución elegante usando plantillas, pero todavía no puedo entenderlo.

ACTUALIZACIÓN: Gracias por las sugerencias: debería haber dejado en claro que las enumeraciones están definidas en un encabezado de biblioteca de un tercero, por lo que no quiero tener que cambiar la definición de ellas.

Mi intuición ahora es evitar plantillas y hacer algo como esto:

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

Si desea que los enum se nombren a sí mismos como cadenas, consulte esta publicación . De lo contrario, un std::map funcionará muy bien. (No tiene sentido copiar los literales de cadena a std :: strings en el mapa)

Para obtener más azúcar sintáctico, a continuación se explica cómo escribir una clase map_init. El objective es permitir

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

La template map_init(T&) función template map_init(T&) devuelve un map_init_helper . map_init_helper almacena un T & y define el trivial map_init_helper& operator()(typename T::key_type const&, typename T::value_type const&) . (Devolver *this desde el operator() permite el encadenamiento del operator() , como el operator< < en 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); } 

Como la función y la clase auxiliar están modeladas, puede usarlas para cualquier mapa o estructura tipo mapa. Es decir, también puede agregar entradas a std::unordered_map

Si no le gusta escribir estos ayudantes, boost :: assign ofrece la misma funcionalidad de forma predeterminada.

La solución de MSalters es buena, pero básicamente implementa de nuevo boost::assign::map_list_of . Si tiene impulso, puede usarlo directamente:

 #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 automáticamente una forma de otra.

Fuente:

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

Generado:

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

Si los valores de enum son grandes, entonces un formulario generado podría usar unordered_map <> o plantillas como lo sugiere Constantin.

Fuente:

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

Generado:

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

Ejemplo:

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

Sugiero que una combinación de usar X-macros sea la mejor solución y las siguientes funciones de plantilla:

Para pedir prestado de marcinkoziukmyopenidcom y extendido

 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) 

Recuerdo haber respondido esto en otro lugar en StackOverflow. Repitiéndolo aquí. Básicamente es una solución basada en macros variadas, y es bastante fácil de usar:

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

Para usarlo en tu código, simplemente hazlo:

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

Si desea obtener representaciones de MyEnum variables de MyEnum , las plantillas no lo cortarán. La plantilla se puede especializar en valores integrales conocidos en tiempo de comstackción.

Sin embargo, si eso es lo que quieres, intenta:

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

Esto es detallado, pero detectará errores como el que hizo en cuestión: su case VAL1 está duplicado.

Uso esta solución que reproduzco a continuación:

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

Estaría tentado de tener un mapa m, e incluirlo en la enumeración.

configuración con m [MyEnum.VAL1] = “Value 1”;

y todo está hecho.

He requerido esta funcionalidad varias veces para depurar / analizar código de otros. Para esto, he escrito un script Perl que genera una clase con varios métodos sobrecargados toString . Cada método toString toma un Enum como argumento y devuelve const char* .

Por supuesto, el script no analiza C ++ para las enumeraciones en sí, sino que usa ctags para generar la tabla de símbolos.

El script de Perl está aquí: http://heinitz-it.de/download/enum2string/enum2string.pl.html

Tus respuestas me inspiraron a escribir algunas macros yo mismo. Mis requisitos fueron los siguientes:

  1. solo escriba cada valor de la enumeración una vez, por lo que no hay listas dobles para mantener

  2. no guarde los valores enum en un archivo separado que más tarde sea #incluido, para que pueda escribirlo donde quiera

  3. no reemplace la enumeración en sí, todavía quiero tener definido el tipo de enumeración, pero además de eso, quiero poder asignar cada nombre enum a la cadena correspondiente (para no afectar el código heredado)

  4. la búsqueda debe ser rápida, preferiblemente sin switch-case, para esas enormes enumeraciones

Este código crea una enumeración clásica con algunos valores. Además crea como std :: map que mapea cada valor enum a su nombre (es decir, mapa [E_SUNDAY] = “E_SUNDAY”, etc.)

Ok, aquí está el código ahora:

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 // este es el archivo que desea incluir cada vez que necesite hacer esto, utilizará las macros que contiene:

 #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 // este es un ejemplo de cómo usarlo para crear una enumeración personalizada:

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

Aclamaciones.

Aquí hay un bash de obtener < < y >> operadores de flujo en enum automáticamente con un comando de macro de una sola línea …

Definiciones:

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

Sin embargo, no estoy seguro sobre las limitaciones de este esquema ... ¡los comentarios son bienvenidos!

Solo quería mostrar esta posible solución elegante usando macros. Esto no resuelve el problema, pero creo que es una buena manera de volver a hablar sobre el 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; } 

---- EDIT ----

Después de algunas investigaciones en Internet y algunos problemas propios, llegué a la siguiente solución:

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

Solo quería publicarlo, tal vez alguien podría encontrar esta solución útil. No hay necesidad de clases de plantillas sin necesidad de c + + 11 y no hay necesidad de boost, por lo que esto también podría usarse para una simple C.

---- EDIT2 ----

la tabla de información puede producir algunos problemas cuando se usan más de 2 enumeraciones (problema del comstackdor). La siguiente solución alternativa funcionó:

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

Arriba está mi solución simple. Uno de sus beneficios es el ‘NUM’ que controla el tamaño de la matriz de mensajes, también evita el acceso fuera del límite (si lo usa con prudencia).

También puede definir una función para obtener la cadena:

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

Además de mi solución, encontré la siguiente bastante interesante. En general, resolvió el problema de sincronización del anterior.

Diapositivas aquí: http://www.slideshare.net/arunksaha/touchless-enum-tostring-28684724

Codifique aquí: https://github.com/arunksaha/enum_to_string

en el encabezado:

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

en el archivo .cpp:

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

Advertencia: no maneje el índice de matriz malo. 🙂 Pero puede agregar fácilmente una función para verificar la enumeración antes de obtener la cadena de la matriz.

Recientemente tuve el mismo problema con una librería de proveedores (Fincad). Afortunadamente, el proveedor proporcionó doucumentation xml para todas las enumeraciones. Terminé generando un mapa para cada tipo de enumeración y proporcionando una función de búsqueda para cada enumeración. Esta técnica también te permite interceptar una búsqueda fuera del rango de la enumeración.

Estoy seguro de que Swig podría hacer algo similar por ti, pero me complace proporcionarte utilidades de generación de código que están escritas en Ruby.

Aquí hay una muestra del código:

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

Parece que quieres ir por el otro camino (enum a string, en lugar de string a enum), pero esto debería ser trivial para revertir.

-Pizca

Vea si la siguiente syntax le conviene:

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

Si lo hace, entonces es posible que desee ver este artículo:
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 es un atributo en GCC / LLVM, entonces puedes usarlo así:

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