¿Hay una manera simple de convertir C ++ enum en una cadena?

Supongamos que tenemos algunas enumeraciones nombradas:

enum MyEnum { FOO, BAR = 0x50 }; 

Lo que busqué en Google es un script (cualquier idioma) que escanea todos los encabezados de mi proyecto y genera un encabezado con una función por enumeración.

 char* enum_to_string(MyEnum t); 

Y una implementación con algo como esto:

 char* enum_to_string(MyEnum t){ switch(t){ case FOO: return "FOO"; case BAR: return "BAR"; default: return "INVALID ENUM"; } } 

El gotcha es realmente con enumeraciones definidas por tipo, y enumeraciones de estilo C sin nombre. ¿Alguien sabe algo de esto?

EDIT: la solución no debe modificar mi fuente, a excepción de las funciones generadas. Las enumeraciones están en una API, por lo que utilizar las soluciones propuestas hasta ahora no es una opción.

Es posible que desee comprobar GCCXML .

Ejecutar GCCXML en su código de muestra produce:

          

Puede usar cualquier idioma que prefiera para extraer las tags Enumeration y EnumValue y generar el código deseado.

X-macros son la mejor solución. Ejemplo:

 #include  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 }; std::ostream& operator<<(std::ostream& os, enum Colours c) { if (c >= ColoursCount || c < 0) return os << "???"; return os << colours_str[c]; } int main() { std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl; } 

colours.def:

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

Sin embargo, generalmente prefiero el siguiente método, por lo que es posible modificar un poco la cadena.

 #define X(a, b) a, #define X(a, b) b, X(Red, "red") X(Green, "green") // etc. 

@hydroo: Sin el archivo adicional:

 #define SOME_ENUM(DO) \ DO(Foo) \ DO(Bar) \ DO(Baz) #define MAKE_ENUM(VAR) VAR, enum MetaSyntacticVariable{ SOME_ENUM(MAKE_ENUM) }; #define MAKE_STRINGS(VAR) #VAR, const char* const MetaSyntacticVariableNames[] = { SOME_ENUM(MAKE_STRINGS) }; 

Lo que tiendo a hacer es crear una matriz C con los nombres en el mismo orden y posición que los valores enum.

p.ej.

 enum colours { red, green, blue }; const char *colour_names[] = { "red", "green", "blue" }; 

entonces puede usar la matriz en lugares donde desee un valor legible para el ser humano, por ejemplo

 colours mycolour = red; cout << "the colour is" << colour_names[mycolour]; 

Puede experimentar un poco con el operador de stringing (vea # en su referencia de preprocesador) que hará lo que quiera, en algunas circunstancias, por ejemplo:

 #define printword(XX) cout << #XX; printword(red); 

imprimirá "rojo" a stdout. Lamentablemente, no funcionará para una variable (ya que obtendrá el nombre de la variable impreso)

Tengo una macro increíblemente fácil de usar que hace esto de una manera completamente seca. Se trata de macros variadas y algunos simples análisis de magia. Aquí va:

 #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 usar esto en tu código, simplemente hazlo:

 AWESOME_MAKE_ENUM(Animal, DOG, CAT, HORSE ); 

QT es capaz de extraer eso de (gracias al comstackdor de metaobjetos): enlace

Acabo de reinventar esta rueda hoy, y pensé en compartirla.

Esta implementación no requiere ningún cambio en el código que define las constantes, que pueden ser enumeraciones o #define s o cualquier otra cosa que dependa de un entero; en mi caso, tenía símbolos definidos en términos de otros símbolos. También funciona bien con valores dispersos. Incluso permite múltiples nombres para el mismo valor, devolviendo siempre el primero. El único inconveniente es que se requiere que hagas una tabla de las constantes, que pueden quedar desactualizadas a medida que se agregan nuevas, por ejemplo.

 struct IdAndName { int id; const char * name; bool operator<(const IdAndName &rhs) const { return id < rhs.id; } }; #define ID_AND_NAME(x) { x, #x } const char * IdToName(int id, IdAndName *table_begin, IdAndName *table_end) { if ((table_end - table_begin) > 1 && table_begin[0].id > table_begin[1].id) std::stable_sort(table_begin, table_end); IdAndName searchee = { id, NULL }; IdAndName *p = std::lower_bound(table_begin, table_end, searchee); return (p == table_end || p->id != id) ? NULL : p->name; } template const char * IdToName(int id, IdAndName (&table)[N]) { return IdToName(id, &table[0], &table[N]); } 

Un ejemplo de cómo lo usarías:

 static IdAndName WindowsErrorTable[] = { ID_AND_NAME(INT_MAX), // flag value to indicate unsorted table ID_AND_NAME(NO_ERROR), ID_AND_NAME(ERROR_INVALID_FUNCTION), ID_AND_NAME(ERROR_FILE_NOT_FOUND), ID_AND_NAME(ERROR_PATH_NOT_FOUND), ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES), ID_AND_NAME(ERROR_ACCESS_DENIED), ID_AND_NAME(ERROR_INVALID_HANDLE), ID_AND_NAME(ERROR_ARENA_TRASHED), ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY), ID_AND_NAME(ERROR_INVALID_BLOCK), ID_AND_NAME(ERROR_BAD_ENVIRONMENT), ID_AND_NAME(ERROR_BAD_FORMAT), ID_AND_NAME(ERROR_INVALID_ACCESS), ID_AND_NAME(ERROR_INVALID_DATA), ID_AND_NAME(ERROR_INVALID_DRIVE), ID_AND_NAME(ERROR_CURRENT_DIRECTORY), ID_AND_NAME(ERROR_NOT_SAME_DEVICE), ID_AND_NAME(ERROR_NO_MORE_FILES) }; const char * error_name = IdToName(GetLastError(), WindowsErrorTable); 

La función IdToName basa en std::lower_bound para hacer búsquedas rápidas, lo que requiere que la tabla sea ordenada. Si las primeras dos entradas de la tabla están desordenadas, la función lo ordenará automáticamente.

Editar: Un comentario me hizo pensar en otra forma de utilizar el mismo principio. Una macro simplifica la generación de una statement de switch grande.

 #define ID_AND_NAME(x) case x: return #x const char * WindowsErrorToName(int id) { switch(id) { ID_AND_NAME(ERROR_INVALID_FUNCTION); ID_AND_NAME(ERROR_FILE_NOT_FOUND); ID_AND_NAME(ERROR_PATH_NOT_FOUND); ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES); ID_AND_NAME(ERROR_ACCESS_DENIED); ID_AND_NAME(ERROR_INVALID_HANDLE); ID_AND_NAME(ERROR_ARENA_TRASHED); ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY); ID_AND_NAME(ERROR_INVALID_BLOCK); ID_AND_NAME(ERROR_BAD_ENVIRONMENT); ID_AND_NAME(ERROR_BAD_FORMAT); ID_AND_NAME(ERROR_INVALID_ACCESS); ID_AND_NAME(ERROR_INVALID_DATA); ID_AND_NAME(ERROR_INVALID_DRIVE); ID_AND_NAME(ERROR_CURRENT_DIRECTORY); ID_AND_NAME(ERROR_NOT_SAME_DEVICE); ID_AND_NAME(ERROR_NO_MORE_FILES); default: return NULL; } } 
 #define stringify( name ) # name enum MyEnum { ENUMVAL1 }; ...stuff... stringify(EnumName::ENUMVAL1); // Returns MyEnum::ENUMVAL1 

Discusión adicional sobre este método

Trucos de la directiva de preprocesador para los recién llegados

Interesante para ver la cantidad de formas. Aquí hay uno que utilicé hace mucho tiempo:

en el archivo myenummap.h:

 #include  #include  enum test{ one, two, three, five=5, six, seven }; struct mymap : std::map { mymap() { this->operator[]( one ) = "ONE"; this->operator[]( two ) = "TWO"; this->operator[]( three ) = "THREE"; this->operator[]( five ) = "FIVE"; this->operator[]( six ) = "SIX"; this->operator[]( seven ) = "SEVEN"; }; ~mymap(){}; }; 

en main.cpp

 #include "myenummap.h" ... mymap nummap; std::cout<< nummap[ one ] << std::endl; 

No es const, pero es conveniente.

Aquí hay otra manera que usa las características de C ++ 11. Esto es const, no hereda un contenedor STL y es un poco más ordenado:

 #include  #include  #include  #include  //These stay together and must be modified together enum test{ one, two, three, five=5, six, seven }; std::string enum_to_str(test const& e) { typedef std::pair mapping; auto m = [](test const& e,std::string const& s){return mapping(static_cast(e),s);}; std::vector const nummap = { m(one,"one"), m(two,"two"), m(three,"three"), m(five,"five"), m(six,"six"), m(seven,"seven"), }; for(auto i : nummap) { if(i.first==static_cast(e)) { return i.second; } } return ""; } int main() { // std::cout<< enum_to_str( 46 ) << std::endl; //compilation will fail std::cout<< "Invalid enum to string : [" << enum_to_str( test(46) ) << "]"< 
 #include  #include  #include  #include  #include  #include  #define SMART_ENUM(EnumName, ...) \ class EnumName \ { \ private: \ static std::map nameMap; \ public: \ enum {__VA_ARGS__}; \ private: \ static std::map initMap() \ { \ using namespace std; \ \ int val = 0; \ string buf_1, buf_2, str = #__VA_ARGS__; \ replace(str.begin(), str.end(), '=', ' '); \ stringstream stream(str); \ vector strings; \ while (getline(stream, buf_1, ',')) \ strings.push_back(buf_1); \ map tmp; \ for(vector::iterator it = strings.begin(); \ it != strings.end(); \ ++it) \ { \ buf_1.clear(); buf_2.clear(); \ stringstream localStream(*it); \ localStream>> buf_1 >> buf_2; \ if(buf_2.size() > 0) \ val = atoi(buf_2.c_str()); \ tmp[val++] = buf_1; \ } \ return tmp; \ } \ public: \ static std::string toString(int aInt) \ { \ return nameMap[aInt]; \ } \ }; \ std::map \ EnumName::nameMap = EnumName::initMap(); 

Uso:

 SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN) cout< 

Esto se puede hacer en C ++ 11

 #include  enum MyEnum { AA, BB, CC, DD }; static std::map< MyEnum, const char * > info = { {AA, "This is an apple"}, {BB, "This is a book"}, {CC, "This is a coffee"}, {DD, "This is a door"} }; void main() { std::cout << info[AA] << endl << info[BB] << endl << info[CC] << endl << info[DD] << endl; } 

Otra respuesta: en algunos contextos, tiene sentido definir su enumeración en un formato que no sea de código, como un archivo CSV, YAML o XML, y luego generar tanto el código de enumeración de C ++ como el código de cadena de la definición. Este enfoque puede o no ser práctico en su aplicación, pero es algo a tener en cuenta.

Esta es una modificación de @ user3360260 respuesta. Tiene las siguientes características nuevas

  • MyEnum fromString(const string&) soporte
  • comstack con VisualStudio 2012
  • el enum es un tipo real de POD (no solo declaraciones const), por lo que puede asignarlo a una variable.
  • Se agregó una característica de “rango” de C ++ (en forma de vector) para permitir la iteración de “foreach” sobre enum

Uso:

 SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN) MyEnum foo = MyEnum::TWO; cout << MyEnum::toString(foo); // static method cout << foo.toString(); // member method cout << MyEnum::toString(MyEnum::TWO); cout << MyEnum::toString(10); MyEnum foo = myEnum::fromString("TWO"); // C++11 iteration over all values for( auto x : MyEnum::allValues() ) { cout << x.toString() << endl; } 

Aquí está el código

 #define SMART_ENUM(EnumName, ...) \ class EnumName \ { \ public: \ EnumName() : value(0) {} \ EnumName(int x) : value(x) {} \ public: \ enum {__VA_ARGS__}; \ private: \ static void initMap(std::map& tmp) \ { \ using namespace std; \ \ int val = 0; \ string buf_1, buf_2, str = #__VA_ARGS__; \ replace(str.begin(), str.end(), '=', ' '); \ stringstream stream(str); \ vector strings; \ while (getline(stream, buf_1, ',')) \ strings.push_back(buf_1); \ for(vector::iterator it = strings.begin(); \ it != strings.end(); \ ++it) \ { \ buf_1.clear(); buf_2.clear(); \ stringstream localStream(*it); \ localStream>> buf_1 >> buf_2; \ if(buf_2.size() > 0) \ val = atoi(buf_2.c_str()); \ tmp[val++] = buf_1; \ } \ } \ int value; \ public: \ operator int () const { return value; } \ std::string toString(void) const { \ return toString(value); \ } \ static std::string toString(int aInt) \ { \ return nameMap()[aInt]; \ } \ static EnumName fromString(const std::string& s) \ { \ auto it = find_if(nameMap().begin(), nameMap().end(), [s](const std::pair& p) { \ return p.second == s; \ }); \ if (it == nameMap().end()) { \ /*value not found*/ \ throw EnumName::Exception(); \ } else { \ return EnumName(it->first); \ } \ } \ class Exception : public std::exception {}; \ static std::map& nameMap() { \ static std::map nameMap0; \ if (nameMap0.size() ==0) initMap(nameMap0); \ return nameMap0; \ } \ static std::vector allValues() { \ std::vector x{ __VA_ARGS__ }; \ return x; \ } \ bool operator<(const EnumName a) const { return (int)*this < (int)a; } \ }; 

Tenga en cuenta que la conversión a String es una búsqueda rápida, mientras que la conversión de String es una búsqueda lineal lenta. Pero las cadenas son tan caras de todos modos (y el archivo asociado IO), no sentí la necesidad de optimizar o usar un bimap.

La solución macro de Suma es agradable. Sin embargo, no necesita tener dos macro diferentes. C ++ felizmente incluirá un encabezado dos veces. Solo deja afuera el protector de inclusión.

Entonces tendrías un foobar.h definiendo solo

 ENUM(Foo, 1) ENUM(Bar, 2) 

y lo incluirías así:

 #define ENUMFACTORY_ARGUMENT "foobar.h" #include "enumfactory.h" 

enumfactory.h hará 2 #include ENUMFACTORY_ARGUMENT s. En la primera ronda, expande ENUM como DECLARE_ENUM de Suma; en la segunda ronda, ENUM funciona como DEFINE_ENUM .

También puede incluir enumfactory.h varias veces, siempre y cuando pase en # definiciones diferentes para ENUMFACTORY_ARGUMENT

Tenga en cuenta que su función de conversión idealmente debería ser devolver un const char *.

Si puede permitirse poner sus enumeraciones en sus archivos de encabezado separados, quizás pueda hacer algo como esto con macros (oh, esto será feo):

 #include "enum_def.h" #include "colour.h" #include "enum_conv.h" #include "colour.h" 

Donde enum_def.h tiene:

 #undef ENUM_START #undef ENUM_ADD #undef ENUM_END #define ENUM_START(NAME) enum NAME { #define ENUM_ADD(NAME, VALUE) NAME = VALUE, #define ENUM_END }; 

Y enum_conv.h tiene:

 #undef ENUM_START #undef ENUM_ADD #undef ENUM_END #define ENUM_START(NAME) const char *##NAME##_to_string(NAME val) { switch (val) { #define ENUM_ADD(NAME, VALUE) case NAME: return #NAME; #define ENUM_END default: return "Invalid value"; } } 

Y finalmente, colour.h tiene:

 ENUM_START(colour) ENUM_ADD(red, 0xff0000) ENUM_ADD(green, 0x00ff00) ENUM_ADD(blue, 0x0000ff) ENUM_END 

Y puede usar la función de conversión como:

 printf("%s", colour_to_string(colour::red)); 

Esto es feo, pero es la única manera (en el nivel de preprocesador) que le permite definir su enumeración en un solo lugar en su código. Por lo tanto, su código no es propenso a errores debido a modificaciones en la enumeración. Tu definición de enum y la función de conversión siempre estarán sincronizadas. Sin embargo, repito, esto es feo 🙂

Lo hago con clases de envoltura enum lado a lado separadas que se generan con macros. Hay varias ventajas:

  • Puede generarlos para enumeraciones que no defino (por ejemplo: enumeraciones de encabezado de plataforma OS)
  • Puede incorporar la verificación de rango en la clase contenedora
  • Puede hacer un formateo “más inteligente” con enums de campo de bit

La desventaja, por supuesto, es que necesito duplicar los valores enum en las clases de formateador, y no tengo ningún script para generarlos. Aparte de eso, parece funcionar bastante bien.

Aquí hay un ejemplo de una enumeración de mi base de código, sin el código de marco que implementa las macros y las plantillas, pero puede obtener la idea:

 enum EHelpLocation { HELP_LOCATION_UNKNOWN = 0, HELP_LOCAL_FILE = 1, HELP_HTML_ONLINE = 2, }; class CEnumFormatter_EHelpLocation : public CEnumDefaultFormatter< EHelpLocation > { public: static inline CString FormatEnum( EHelpLocation eValue ) { switch ( eValue ) { ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCATION_UNKNOWN ); ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCAL_FILE ); ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_HTML_ONLINE ); default: return FormatAsNumber( eValue ); } } }; DECLARE_RANGE_CHECK_CLASS( EHelpLocation, CRangeInfoSequential< HELP_HTML_ONLINE > ); typedef ESmartEnum< EHelpLocation, HELP_LOCATION_UNKNOWN, CEnumFormatter_EHelpLocation, CRangeInfo_EHelpLocation > SEHelpLocation; 

La idea es, en vez de usar EHelpLocation, usar SEHelpLocation; todo funciona igual, pero obtienes un control de rango y un método ‘Format ()’ en la variable enum. Si necesita formatear un valor independiente, puede usar CEnumFormatter_EHelpLocation :: FormatEnum (…).

Espero que esto sea útil. Me doy cuenta de que esto tampoco aborda la pregunta original sobre un guión para generar realmente la otra clase, pero espero que la estructura ayude a alguien a tratar de resolver el mismo problema, o escriba ese guión.

Aquí una solución de un archivo (basada en la respuesta elegante de @Marcin:

 #include  #define ENUM_TXT \ X(Red) \ X(Green) \ X(Blue) \ X(Cyan) \ X(Yellow) \ X(Magenta) \ enum Colours { # define X(a) a, ENUM_TXT # undef X ColoursCount }; char const* const colours_str[] = { # define X(a) #a, ENUM_TXT # undef X 0 }; std::ostream& operator<<(std::ostream& os, enum Colours c) { if (c >= ColoursCount || c < 0) return os << "???"; return os << colours_str[c] << std::endl; } int main() { std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl; } 

Esta fue mi solución con BOOST:

 #include  #define X_STR_ENUM_TOSTRING_CASE(r, data, elem) \ case elem : return BOOST_PP_STRINGIZE(elem); #define X_ENUM_STR_TOENUM_IF(r, data, elem) \ else if(data == BOOST_PP_STRINGIZE(elem)) return elem; #define STR_ENUM(name, enumerators) \ enum name { \ BOOST_PP_SEQ_ENUM(enumerators) \ }; \ \ inline const QString enumToStr(name v) \ { \ switch (v) \ { \ BOOST_PP_SEQ_FOR_EACH( \ X_STR_ENUM_TOSTRING_CASE, \ name, \ enumerators \ ) \ \ default: \ return "[Unknown " BOOST_PP_STRINGIZE(name) "]"; \ } \ } \ \ template  \ inline const T strToEnum(QString v); \ \ template <> \ inline const name strToEnum(QString v) \ { \ if(v=="") \ throw std::runtime_error("Empty enum value"); \ \ BOOST_PP_SEQ_FOR_EACH( \ X_ENUM_STR_TOENUM_IF, \ v, \ enumerators \ ) \ \ else \ throw std::runtime_error( \ QString("[Unknown value %1 for enum %2]") \ .arg(v) \ .arg(BOOST_PP_STRINGIZE(name)) \ .toStdString().c_str()); \ } 

Para crear enum, declare:

 STR_ENUM ( SERVICE_RELOAD, (reload_log) (reload_settings) (reload_qxml_server) ) 

Para conversiones:

 SERVICE_RELOAD serviceReloadEnum = strToEnum("reload_log"); QString serviceReloadStr = enumToStr(reload_log); 

Un problema con la respuesta 0 es que los valores binarios enum no necesariamente comienzan en 0 y no son necesariamente contiguos.

Cuando lo necesito, generalmente:

  • tire de la definición enum en mi fuente
  • edítelo para obtener solo los nombres
  • haga una macro para cambiar el nombre a la cláusula de caso en la pregunta, aunque típicamente en una línea: caso foo: return “foo”;
  • agregue el interruptor, predeterminado y otra syntax para hacerlo legal

La siguiente secuencia de comandos de ruby ​​intenta analizar los encabezados y construye las fonts necesarias junto con los encabezados originales.

 #! /usr/bin/env ruby # Let's "parse" the headers # Note that using a regular expression is rather fragile # and may break on some inputs GLOBS = [ "toto/*.h", "tutu/*.h", "tutu/*.hxx" ] enums = {} GLOBS.each { |glob| Dir[glob].each { |header| enums[header] = File.open(header, 'rb') { |f| f.read }.scan(/enum\s+(\w+)\s+\{\s*([^}]+?)\s*\}/m).collect { |enum_name, enum_key_and_values| [ enum_name, enum_key_and_values.split(/\s*,\s*/).collect { |enum_key_and_value| enum_key_and_value.split(/\s*=\s*/).first } ] } } } # Now we build a .h and .cpp alongside the parsed headers # using the template engine provided with ruby require 'erb' template_h = ERB.new <<-EOS #ifndef <%= enum_name %>_to_string_h_ #define <%= enum_name %>_to_string_h_ 1 #include "<%= header %>" char* enum_to_string(<%= enum_name %> e); #endif EOS template_cpp = ERB.new <<-EOS #include "<%= enum_name %>_to_string.h" char* enum_to_string(<%= enum_name %> e) { switch (e) {<% enum_keys.each do |enum_key| %> case <%= enum_key %>: return "<%= enum_key %>";<% end %> default: return "INVALID <%= enum_name %> VALUE"; } } EOS enums.each { |header, enum_name_and_keys| enum_name_and_keys.each { |enum_name, enum_keys| File.open("#{File.dirname(header)}/#{enum_name}_to_string.h", 'wb') { |built_h| built_h.write(template_h.result(binding)) } File.open("#{File.dirname(header)}/#{enum_name}_to_string.cpp", 'wb') { |built_cpp| built_cpp.write(template_cpp.result(binding)) } } } 

El uso de expresiones regulares hace que este “analizador” sea bastante frágil, puede que no sea capaz de manejar sus encabezados específicos correctamente.

Digamos que tiene un encabezado toto / ah, que contiene definiciones para enumeraciones MyEnum y MyEnum2. El script se comstackrá

 toto/MyEnum_to_string.h toto/MyEnum_to_string.cpp toto/MyEnum2_to_string.h toto/MyEnum2_to_string.cpp 

Las soluciones más robustas serían:

  • Cree todas las fonts que definen las enumeraciones y sus operaciones desde otra fuente. Esto significa que definirá sus enumeraciones en un archivo XML / YML / lo que es mucho más fácil de analizar que C / C ++.
  • Use un comstackdor real como el sugerido por Avdi.
  • Use macros de preprocesador con o sin plantillas.

Es un software inédito pero parece que BOOST_ENUM de Frank Laub podría encajar en la factura. La parte que me gusta es que puedes definir una enumeración dentro del scope de una clase que la mayoría de las enumeraciones basadas en macros normalmente no te dejan hacer. Está ubicado en Boost Vault en: http://www.boostpro.com/vault/index.php?action=downloadfile&filename=enum_rev4.6.zip&directory=&. No ha visto ningún desarrollo desde 2006, así que no saber qué tan bien se comstack con las nuevas versiones de Boost. Busque en libs / test un ejemplo de uso.

Esa es prácticamente la única forma en que se puede hacer (una serie de cadenas también podría funcionar).

El problema es que, una vez que se comstack un progtwig C, el valor binario de la enumeración es todo lo que se usa y el nombre desaparece.

Aquí hay un progtwig de CLI que escribí para convertir fácilmente enumeraciones en cadenas. Es fácil de usar, y tarda unos 5 segundos en hacerlo (incluido el tiempo de cd al directorio que contiene el progtwig, luego lo ejecuta y le pasa el archivo que contiene la enumeración).

Descargue aquí: http://www.mediafire.com/?nttignoozzz

El tema de discusión aquí: http://cboard.cprogramming.com/projects-job-recruitment/127488-free-program-im-sharing-convertenumtostrings.html

Ejecute el progtwig con el argumento “–help” para obtener una descripción de cómo usarlo.

No hace mucho tiempo hice un truco para que las enumeraciones se mostraran correctamente en QComboBox y para tener una definición de enum y representaciones de cadenas como una statement

 #pragma once #include  namespace enumeration { struct enumerator_base : boost::noncopyable { typedef boost::unordered_map kv_storage_t; typedef kv_storage_t::value_type kv_type; kv_storage_t const & kv() const { return storage_; } LPCWSTR name(int i) const { kv_storage_t::const_iterator it = storage_.find(i); if(it != storage_.end()) return it->second.c_str(); return L"empty"; } protected: kv_storage_t storage_; }; template struct enumerator; template struct enum_singleton : enumerator_base { static enumerator_base const & instance() { static D inst; return inst; } }; } #define QENUM_ENTRY(K, V, N) K, N storage_.insert(std::make_pair((int)K, V)); #define QBEGIN_ENUM(NAME, C) \ enum NAME \ { \ C \ } \ }; \ } \ #define QEND_ENUM(NAME) \ }; \ namespace enumeration \ { \ template<> \ struct enumerator\ : enum_singleton< enumerator >\ { \ enumerator() \ { //usage /* QBEGIN_ENUM(test_t, QENUM_ENTRY(test_entry_1, L"number uno", QENUM_ENTRY(test_entry_2, L"number dos", QENUM_ENTRY(test_entry_3, L"number tres", QEND_ENUM(test_t))))) */ 

Ahora tiene enumeration::enum_singleton::instance() capaz de convertir enumeraciones a cadenas. Si reemplaza kv_storage_t con boost::bimap , también podrá realizar la conversión hacia atrás. Se introdujo la clase base común para convertidor para almacenarlo en el objeto Qt, porque los objetos Qt no podían ser plantillas

Apariencia previa

Como variante, use lib simple> http://codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C

En el código

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

agregar líneas

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

Funciona bien, si los valores en enum no son duplicados .

Ejemplo de uso

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

y viceversa

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

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

Not sure about the limitations of this scheme though... comments are welcome!

 #include  #include  #define IDMAP(x) (x,#x) std::map enToStr; class mapEnumtoString { public: mapEnumtoString(){ } mapEnumtoString& operator()(int i,std::string str) { enToStr[i] = str; return *this; } public: std::string operator [] (int i) { return enToStr[i]; } }; mapEnumtoString k; mapEnumtoString& init() { return k; } int main() { init() IDMAP(1) IDMAP(2) IDMAP(3) IDMAP(4) IDMAP(5); std::cout< 

Check this post:

Class implementation of C++ Enums

it contains class implementation of c++ enum.

I want to post this in case someone finds it useful.

In my case, I simply need to generate ToString() and FromString() functions for a single C++11 enum from a single .hpp file.

I wrote a python script that parses the header file containing the enum items and generates the functions in a new .cpp file.

You can add this script in CMakeLists.txt with execute_process , or as a pre-build event in Visual Studio. The .cpp file will be automatically generated, without the need to manually update it each time a new enum item is added.

generate_enum_strings.py

 # This script is used to generate strings from C++ enums import re import sys import os fileName = sys.argv[1] enumName = os.path.basename(os.path.splitext(fileName)[0]) with open(fileName, 'r') as f: content = f.read().replace('\n', '') searchResult = re.search('enum(.*)\{(.*?)\};', content) tokens = searchResult.group(2) tokens = tokens.split(',') tokens = map(str.strip, tokens) tokens = map(lambda token: re.search('([a-zA-Z0-9_]*)', token).group(1), tokens) textOut = '' textOut += '\n#include "' + enumName + '.hpp"\n\n' textOut += 'namespace myns\n' textOut += '{\n' textOut += ' std::string ToString(ErrorCode errorCode)\n' textOut += ' {\n' textOut += ' switch (errorCode)\n' textOut += ' {\n' for token in tokens: textOut += ' case ' + enumName + '::' + token + ':\n' textOut += ' return "' + token + '";\n' textOut += ' default:\n' textOut += ' return "Last";\n' textOut += ' }\n' textOut += ' }\n' textOut += '\n' textOut += ' ' + enumName + ' FromString(const std::string &errorCode)\n' textOut += ' {\n' textOut += ' if ("' + tokens[0] + '" == errorCode)\n' textOut += ' {\n' textOut += ' return ' + enumName + '::' + tokens[0] + ';\n' textOut += ' }\n' for token in tokens[1:]: textOut += ' else if("' + token + '" == errorCode)\n' textOut += ' {\n' textOut += ' return ' + enumName + '::' + token + ';\n' textOut += ' }\n' textOut += '\n' textOut += ' return ' + enumName + '::Last;\n' textOut += ' }\n' textOut += '}\n' fileOut = open(enumName + '.cpp', 'w') fileOut.write(textOut) 

Ejemplo:

ErrorCode.hpp

 #pragma once #include  #include  namespace myns { enum class ErrorCode : uint32_t { OK = 0, OutOfSpace, ConnectionFailure, InvalidJson, DatabaseFailure, HttpError, FileSystemError, FailedToEncrypt, FailedToDecrypt, EndOfFile, FailedToOpenFileForRead, FailedToOpenFileForWrite, FailedToLaunchProcess, Last }; std::string ToString(ErrorCode errorCode); ErrorCode FromString(const std::string &errorCode); } 

Run python generate_enum_strings.py ErrorCode.hpp

Resultado:

ErrorCode.cpp

 #include "ErrorCode.hpp" namespace myns { std::string ToString(ErrorCode errorCode) { switch (errorCode) { case ErrorCode::OK: return "OK"; case ErrorCode::OutOfSpace: return "OutOfSpace"; case ErrorCode::ConnectionFailure: return "ConnectionFailure"; case ErrorCode::InvalidJson: return "InvalidJson"; case ErrorCode::DatabaseFailure: return "DatabaseFailure"; case ErrorCode::HttpError: return "HttpError"; case ErrorCode::FileSystemError: return "FileSystemError"; case ErrorCode::FailedToEncrypt: return "FailedToEncrypt"; case ErrorCode::FailedToDecrypt: return "FailedToDecrypt"; case ErrorCode::EndOfFile: return "EndOfFile"; case ErrorCode::FailedToOpenFileForRead: return "FailedToOpenFileForRead"; case ErrorCode::FailedToOpenFileForWrite: return "FailedToOpenFileForWrite"; case ErrorCode::FailedToLaunchProcess: return "FailedToLaunchProcess"; case ErrorCode::Last: return "Last"; default: return "Last"; } } ErrorCode FromString(const std::string &errorCode) { if ("OK" == errorCode) { return ErrorCode::OK; } else if("OutOfSpace" == errorCode) { return ErrorCode::OutOfSpace; } else if("ConnectionFailure" == errorCode) { return ErrorCode::ConnectionFailure; } else if("InvalidJson" == errorCode) { return ErrorCode::InvalidJson; } else if("DatabaseFailure" == errorCode) { return ErrorCode::DatabaseFailure; } else if("HttpError" == errorCode) { return ErrorCode::HttpError; } else if("FileSystemError" == errorCode) { return ErrorCode::FileSystemError; } else if("FailedToEncrypt" == errorCode) { return ErrorCode::FailedToEncrypt; } else if("FailedToDecrypt" == errorCode) { return ErrorCode::FailedToDecrypt; } else if("EndOfFile" == errorCode) { return ErrorCode::EndOfFile; } else if("FailedToOpenFileForRead" == errorCode) { return ErrorCode::FailedToOpenFileForRead; } else if("FailedToOpenFileForWrite" == errorCode) { return ErrorCode::FailedToOpenFileForWrite; } else if("FailedToLaunchProcess" == errorCode) { return ErrorCode::FailedToLaunchProcess; } else if("Last" == errorCode) { return ErrorCode::Last; } return ErrorCode::Last; } } 

Well, yet another option. A typical use case is where you need constant for the HTTP verbs as well as using is string version values.

The example:

 int main () { VERB a = VERB::GET; VERB b = VERB::GET; VERB c = VERB::POST; VERB d = VERB::PUT; VERB e = VERB::DELETE; std::cout << a.toString() << std::endl; std::cout << a << std::endl; if ( a == VERB::GET ) { std::cout << "yes" << std::endl; } if ( a == b ) { std::cout << "yes" << std::endl; } if ( a != c ) { std::cout << "no" << std::endl; } } 

The VERB class:

 // ----------------------------------------------------------- // ----------------------------------------------------------- class VERB { private: // private constants enum Verb {GET_=0, POST_, PUT_, DELETE_}; // private string values static const std::string theStrings[]; // private value const Verb value; const std::string text; // private constructor VERB (Verb v) : value(v), text (theStrings[v]) { // std::cout << " constructor \n"; } public: operator const char * () const { return text.c_str(); } operator const std::string () const { return text; } const std::string toString () const { return text; } bool operator == (const VERB & other) const { return (*this).value == other.value; } bool operator != (const VERB & other) const { return ! ( (*this) == other); } // --- static const VERB GET; static const VERB POST; static const VERB PUT; static const VERB DELETE; }; const std::string VERB::theStrings[] = {"GET", "POST", "PUT", "DELETE"}; const VERB VERB::GET = VERB ( VERB::Verb::GET_ ); const VERB VERB::POST = VERB ( VERB::Verb::POST_ ); const VERB VERB::PUT = VERB ( VERB::Verb::PUT_ ); const VERB VERB::DELETE = VERB ( VERB::Verb::DELETE_ ); // end of file