¿Manera fácil de usar variables de tipos enum como cadena en C?

Esto es lo que estoy tratando de hacer:

typedef enum { ONE, TWO, THREE } Numbers; 

Estoy tratando de escribir una función que haría una caja de cambio similar a la siguiente:

 char num_str[10]; int process_numbers_str(Numbers num) { switch(num) { case ONE: case TWO: case THREE: { strcpy(num_str, num); //some way to get the symbolic constant name in here? } break; default: return 0; //no match return 1; } 

En lugar de definir en todos los casos, ¿hay alguna forma de establecerlo usando la variable enum como lo estoy intentando hacer arriba?

No hay una solución incorporada. La manera más fácil es con una matriz de char* donde el valor int de la enumeración indexa una cadena que contiene el nombre descriptivo de esa enumeración. Si tiene una enum dispersa (una que no comienza en 0 o tiene espacios en la numeración) donde algunas de las asignaciones int son lo suficientemente altas como para hacer que una asignación basada en matriz no sea práctica, entonces podría usar una tabla hash en su lugar.

 // Define your enumeration like this (in say numbers.h); ENUM_BEGIN( Numbers ) ENUM(ONE), ENUM(TWO), ENUM(FOUR) ENUM_END( Numbers ) // The macros are defined in a more fundamental .h file (say defs.h); #define ENUM_BEGIN(typ) enum typ { #define ENUM(nam) nam #define ENUM_END(typ) }; // Now in one and only one .c file, redefine the ENUM macros and reinclude // the numbers.h file to build a string table #undef ENUM_BEGIN #undef ENUM #undef ENUM_END #define ENUM_BEGIN(typ) const char * typ ## _name_table [] = { #define ENUM(nam) #nam #define ENUM_END(typ) }; #undef NUMBERS_H_INCLUDED // whatever you need to do to enable reinclusion #include "numbers.h" // Now you can do exactly what you want to do, with no retyping, and for any // number of enumerated types defined with the ENUM macro family // Your code follows; char num_str[10]; int process_numbers_str(Numbers num) { switch(num) { case ONE: case TWO: case THREE: { strcpy(num_str, Numbers_name_table[num]); // eg TWO -> "TWO" } break; default: return 0; //no match return 1; } // Sweet no ? After being frustrated by this for years, I finally came up // with this solution for my most recent project and plan to reuse the idea // forever 

La técnica de Hacer que tanto un identificador de C y una cadena? se puede usar aquí.

Como es habitual en tales preprocesadores, escribir y comprender la parte del preprocesador puede ser difícil e incluye pasar macros a otras macros e implica el uso de operadores # y ##, pero su uso es muy fácil. Encuentro este estilo muy útil para las enumeraciones largas, donde mantener la misma lista dos veces puede ser realmente problemático.

Código de fábrica: typescript solo una vez, generalmente oculto en el encabezado:

enumFactory.h:

 // expansion macro for enum value definition #define ENUM_VALUE(name,assign) name assign, // expansion macro for enum to string conversion #define ENUM_CASE(name,assign) case name: return #name; // expansion macro for string to enum conversion #define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name; /// declare the access function and define enum values #define DECLARE_ENUM(EnumType,ENUM_DEF) \ enum EnumType { \ ENUM_DEF(ENUM_VALUE) \ }; \ const char *GetString(EnumType dummy); \ EnumType Get##EnumType##Value(const char *string); \ /// define the access function names #define DEFINE_ENUM(EnumType,ENUM_DEF) \ const char *GetString(EnumType value) \ { \ switch(value) \ { \ ENUM_DEF(ENUM_CASE) \ default: return ""; /* handle input error */ \ } \ } \ EnumType Get##EnumType##Value(const char *str) \ { \ ENUM_DEF(ENUM_STRCMP) \ return (EnumType)0; /* handle input error */ \ } \ 

Utilizado en la fábrica

someEnum.h:

 #include "enumFactory.h" #define SOME_ENUM(XX) \ XX(FirstValue,) \ XX(SecondValue,) \ XX(SomeOtherValue,=50) \ XX(OneMoreValue,=100) \ DECLARE_ENUM(SomeEnum,SOME_ENUM) 

someEnum.cpp:

 #include "someEnum.h" DEFINE_ENUM(SomeEnum,SOME_ENUM) 

La técnica se puede extender fácilmente para que las macros XX acepten más argumentos, y también se pueden haber preparado más macros para sustituir XX por diferentes necesidades, similares a las tres que he proporcionado en esta muestra.

Comparación con X-Macros usando #include / #define / #undef

Si bien esto es similar a X-Macros que otros han mencionado, creo que esta solución es más elegante ya que no requiere #desdefinir nada, lo que le permite ocultar más cosas complicadas en la fábrica es el archivo de encabezado – el archivo de encabezado es algo que no estás tocando en absoluto cuando necesitas definir una nueva enumeración, por lo tanto, la nueva definición de enum es mucho más corta y más clara.

Definitivamente hay una forma de hacerlo: use macros X () . Estas macros usan el preprocesador C para construir enumeraciones, matrices y bloques de códigos a partir de una lista de datos fuente. Solo necesita agregar nuevos elementos al #define que contiene la macro X (). La instrucción switch se expandiría automáticamente.

Su ejemplo se puede escribir de la siguiente manera:

  // Source data -- Enum, String #define X_NUMBERS \ X(ONE, "one") \ X(TWO, "two") \ X(THREE, "three") ... // Use preprocessor to create the Enum typedef enum { #define X(Enum, String) Enum, X_NUMBERS #undef X } Numbers; ... // Use Preprocessor to expand data into switch statement cases switch(num) { #define X(Enum, String) \ case Enum: strcpy(num_str, String); break; X_NUMBERS #undef X default: return 0; break; } return 1; 

Hay formas más eficientes (es decir, usar X Macros para crear una matriz de cadenas y un índice enum), pero esta es la demostración más simple.

Sé que tiene un par de buenas respuestas sólidas, pero ¿sabe usted sobre el operador # en el preprocesador C?

Te deja hacer esto:

 #define MACROSTR(k) #k typedef enum { kZero, kOne, kTwo, kThree } kConst; static char *kConstStr[] = { MACROSTR(kZero), MACROSTR(kOne), MACROSTR(kTwo), MACROSTR(kThree) }; static void kConstPrinter(kConst k) { printf("%s", kConstStr[k]); } 

C o C ++ no proporcionan esta funcionalidad, aunque la he necesitado a menudo.

El siguiente código funciona, aunque es más adecuado para enums no dispersos.

 typedef enum { ONE, TWO, THREE } Numbers; char *strNumbers[] = {"one","two","three"}; printf ("Value for TWO is %s\n",strNumbers[TWO]); 

Por no escasa, quiero decir que no de la forma

 typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers; 

ya que eso tiene enormes lagunas.

La ventaja de este método es que pone las definiciones de las enumeraciones y las cadenas cerca unas de otras; tener una statement de cambio en una función los anuncia. Esto significa que es menos probable que cambie uno sin el otro.

BESO. Harás todo tipo de cosas de interruptor / caja con tus enumeraciones entonces ¿por qué la impresión debería ser diferente? Olvidar un caso en su rutina de impresión no es un gran problema cuando considera que hay otros 100 lugares donde puede olvidar un caso. Solo compile -Wall, lo que advertirá de coincidencias de casos no exhaustivas. No use “predeterminado” porque eso hará que el cambio sea exhaustivo y no recibirá advertencias. En su lugar, deje que el interruptor salga y trate el caso predeterminado así que …

 const char *myenum_str(myenum e) { switch(e) { case ONE: return "one"; case TWO: return "two"; } return "invalid"; } 

Intente convertir enums de C ++ a cadenas . Los comentarios tienen mejoras que resuelven el problema cuando los elementos enum tienen valores arbitrarios.

El uso del preprocesador boost :: hace posible una solución elegante como la siguiente:

Paso 1: incluye el archivo de encabezado:

 #include "EnumUtilities.h" 

Paso 2: declare el objeto de enumeración con la siguiente syntax:

 MakeEnum( TestData, (x) (y) (z) ); 

Paso 3: usa tus datos:

Obtener la cantidad de elementos:

 td::cout << "Number of Elements: " << TestDataCount << std::endl; 

Obtener la cadena asociada:

 std::cout << "Value of " << TestData2String(x) << " is " << x << std::endl; std::cout << "Value of " << TestData2String(y) << " is " << y << std::endl; std::cout << "Value of " << TestData2String(z) << " is " << z << std::endl; 

Obteniendo el valor enum de la cadena asociada:

 std::cout << "Value of x is " << TestData2Enum("x") << std::endl; std::cout << "Value of y is " << TestData2Enum("y") << std::endl; std::cout << "Value of z is " << TestData2Enum("z") << std::endl; 

Esto se ve limpio y compacto, sin archivos adicionales para incluir. El código que escribí en EnumUtilities.h es el siguiente:

 #include  #include  #define REALLY_MAKE_STRING(x) #x #define MAKE_STRING(x) REALLY_MAKE_STRING(x) #define MACRO1(r, data, elem) elem, #define MACRO1_STRING(r, data, elem) case elem: return REALLY_MAKE_STRING(elem); #define MACRO1_ENUM(r, data, elem) if (REALLY_MAKE_STRING(elem) == eStrEl) return elem; #define MakeEnum(eName, SEQ) \ enum eName { BOOST_PP_SEQ_FOR_EACH(MACRO1, , SEQ) \ last_##eName##_enum}; \ const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \ static std::string eName##2String(const enum eName eel) \ { \ switch (eel) \ { \ BOOST_PP_SEQ_FOR_EACH(MACRO1_STRING, , SEQ) \ default: return "Unknown enumerator value."; \ }; \ }; \ static enum eName eName##2Enum(const std::string eStrEl) \ { \ BOOST_PP_SEQ_FOR_EACH(MACRO1_ENUM, , SEQ) \ return (enum eName)0; \ }; 

Hay algunas limitaciones, es decir, las de boost :: preprocessor. En este caso, la lista de constantes no puede ser mayor que 64 elementos.

Siguiendo la misma lógica, también podrías pensar en crear enum dispersa:

 #define EnumName(Tuple) BOOST_PP_TUPLE_ELEM(2, 0, Tuple) #define EnumValue(Tuple) BOOST_PP_TUPLE_ELEM(2, 1, Tuple) #define MACRO2(r, data, elem) EnumName(elem) EnumValue(elem), #define MACRO2_STRING(r, data, elem) case EnumName(elem): return BOOST_PP_STRINGIZE(EnumName(elem)); #define MakeEnumEx(eName, SEQ) \ enum eName { \ BOOST_PP_SEQ_FOR_EACH(MACRO2, _, SEQ) \ last_##eName##_enum }; \ const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \ static std::string eName##2String(const enum eName eel) \ { \ switch (eel) \ { \ BOOST_PP_SEQ_FOR_EACH(MACRO2_STRING, _, SEQ) \ default: return "Unknown enumerator value."; \ }; \ }; 

En este caso, la syntax es:

 MakeEnumEx(TestEnum, ((x,)) ((y,=1000)) ((z,)) ); 

El uso es similar al anterior (menos la función eName ## 2Enum, que podría intentar extrapolar a partir de la syntax anterior).

Lo probé en Mac y Linux, pero tenga en cuenta que boost :: preprocessor puede no ser totalmente portátil.

Al fusionar algunas de las técnicas aquí, se me ocurrió la forma más simple:

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

Si está utilizando gcc, es posible usar:

 const char * enum_to_string_map[]={ [enum1]='string1', [enum2]='string2'}; 

Entonces solo llama por ejemplo

 enum_to_string_map[enum1] 

Vea las ideas en Mu Dynamics Research Labs – Blog Archive . Encontré esto a principios de este año, olvidé el contexto exacto en el que lo encontré, y lo he adaptado a este código. Podemos debatir los méritos de agregar una E al frente; es aplicable al problema específico abordado, pero no forma parte de una solución general. Guardé esto en mi carpeta de “viñetas”, donde guardo trozos de código interesantes en caso de que los quiera más tarde. Me avergüenza decir que no llevé una nota de dónde vino esta idea en ese momento.

Encabezado: paste1.h

 /* @(#)File: $RCSfile: paste1.h,v $ @(#)Version: $Revision: 1.1 $ @(#)Last changed: $Date: 2008/05/17 21:38:05 $ @(#)Purpose: Automated Token Pasting */ #ifndef JLSS_ID_PASTE_H #define JLSS_ID_PASTE_H /* * Common case when someone just includes this file. In this case, * they just get the various E* tokens as good old enums. */ #if !defined(ETYPE) #define ETYPE(val, desc) E##val, #define ETYPE_ENUM enum { #endif /* ETYPE */ ETYPE(PERM, "Operation not permitted") ETYPE(NOENT, "No such file or directory") ETYPE(SRCH, "No such process") ETYPE(INTR, "Interrupted system call") ETYPE(IO, "I/O error") ETYPE(NXIO, "No such device or address") ETYPE(2BIG, "Arg list too long") /* * Close up the enum block in the common case of someone including * this file. */ #if defined(ETYPE_ENUM) #undef ETYPE_ENUM #undef ETYPE ETYPE_MAX }; #endif /* ETYPE_ENUM */ #endif /* JLSS_ID_PASTE_H */ 

Fuente de ejemplo:

 /* @(#)File: $RCSfile: paste1.c,v $ @(#)Version: $Revision: 1.2 $ @(#)Last changed: $Date: 2008/06/24 01:03:38 $ @(#)Purpose: Automated Token Pasting */ #include "paste1.h" static const char *sys_errlist_internal[] = { #undef JLSS_ID_PASTE_H #define ETYPE(val, desc) desc, #include "paste1.h" 0 #undef ETYPE }; static const char *xerror(int err) { if (err >= ETYPE_MAX || err <= 0) return "Unknown error"; return sys_errlist_internal[err]; } static const char*errlist_mnemonics[] = { #undef JLSS_ID_PASTE_H #define ETYPE(val, desc) [E ## val] = "E" #val, #include "paste1.h" #undef ETYPE }; #include  int main(void) { int i; for (i = 0; i < ETYPE_MAX; i++) { printf("%d: %-6s: %s\n", i, errlist_mnemonics[i], xerror(i)); } return(0); } 

No necesariamente es el uso más limpio del preprocesador C en el mundo, pero evita que se escriba el material varias veces.

Hacer algo tanto un identificador de C como una cadena

Si el índice enum está basado en 0, puede poner los nombres en una matriz de caracteres * e indexarlos con el valor enum.

 #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

streamable_enum clase de plantilla simple streamable_enum que usa los operadores de flujo << y >> y se basa en std::map :

 #ifndef STREAMABLE_ENUM_HPP #define STREAMABLE_ENUM_HPP #include  #include  #include  template  class streamable_enum { public: typedef typename std::map tostr_map_t; typedef typename std::map fromstr_map_t; streamable_enum() {} streamable_enum(E val) : Val_(val) {} operator E() { return Val_; } bool operator==(const streamable_enum& e) { return this->Val_ == e.Val_; } bool operator==(const E& e) { return this->Val_ == e; } static const tostr_map_t& to_string_map() { static tostr_map_t to_str_(get_enum_strings()); return to_str_; } static const fromstr_map_t& from_string_map() { static fromstr_map_t from_str_(reverse_map(to_string_map())); return from_str_; } private: E Val_; static fromstr_map_t reverse_map(const tostr_map_t& eToS) { fromstr_map_t sToE; for (auto pr : eToS) { sToE.emplace(pr.second, pr.first); } return sToE; } }; template  streamable_enum stream_enum(E e) { return streamable_enum(e); } template  typename streamable_enum::tostr_map_t get_enum_strings() { // \todo throw an appropriate exception or display compile error/warning return {}; } template  std::ostream& operator<<(std::ostream& os, streamable_enum e) { auto& mp = streamable_enum::to_string_map(); auto res = mp.find(e); if (res != mp.end()) { os << res->second; } else { os.setstate(std::ios_base::failbit); } return os; } template  std::istream& operator>>(std::istream& is, streamable_enum& e) { std::string str; is >> str; if (str.empty()) { is.setstate(std::ios_base::failbit); } auto& mp = streamable_enum::from_string_map(); auto res = mp.find(str); if (res != mp.end()) { e = res->second; } else { is.setstate(std::ios_base::failbit); } return is; } #endif 

Uso:

 #include "streamable_enum.hpp" using std::cout; using std::cin; using std::endl; enum Animal { CAT, DOG, TIGER, RABBIT }; template <> streamable_enum::tostr_map_t get_enum_strings() { return { { CAT, "Cat"}, { DOG, "Dog" }, { TIGER, "Tiger" }, { RABBIT, "Rabbit" } }; } int main(int argc, char* argv []) { cout << "What animal do you want to buy? Our offering:" << endl; for (auto pr : streamable_enum::to_string_map()) { // Use from_string_map() and pr.first instead cout << " " << pr.second << endl; // to have them sorted in alphabetical order } streamable_enum anim; cin >> anim; if (!cin) { cout << "We don't have such animal here." << endl; } else if (anim == Animal::TIGER) { cout << stream_enum(Animal::TIGER) << " was a joke..." << endl; } else { cout << "Here you are!" << endl; } return 0; } 

Aquí hay una solución que usa macros con las siguientes características:

  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

https://stackoverflow.com/a/20134475/1812866

Pensé que una solución como Boost.Fusion para adaptar estructuras y clases sería agradable, incluso la tenían en algún momento, para usar enumeraciones como una secuencia de fusión.

Así que hice algunas macros pequeñas para generar el código para imprimir las enumeraciones. Esto no es perfecto y no tiene nada que ver con el código repetitivo generado por Boost.Fusion, pero se puede usar como las macros de Boost Fusion. Realmente quiero generar los tipos que necesita Boost.Fusion para integrar en esta infraestructura, lo que permite imprimir los nombres de los miembros de la estructura, pero esto sucederá más adelante, por ahora esto solo son macros:

 #ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP #define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP #include  #include  #include  #include  #include  #include  #define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C( \ R, unused, ENUMERATION_ENTRY) \ case ENUMERATION_ENTRY: \ return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY); \ break; /** * \brief Adapts ENUM to reflectable types. * * \param ENUM_TYPE To be adapted * \param ENUMERATION_SEQ Sequence of enum states */ #define SWISSARMYKNIFE_ADAPT_ENUM(ENUM_TYPE, ENUMERATION_SEQ) \ inline std::string to_string(const ENUM_TYPE& enum_value) { \ switch (enum_value) { \ BOOST_PP_SEQ_FOR_EACH( \ SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C, \ unused, ENUMERATION_SEQ) \ default: \ return BOOST_PP_STRINGIZE(ENUM_TYPE); \ } \ } \ \ inline std::ostream& operator<<(std::ostream& os, const ENUM_TYPE& value) { \ os << to_string(value); \ return os; \ } #endif 

La respuesta anterior a continuación es bastante mala, por favor no la uses. 🙂

Respuesta anterior:

He estado buscando una forma que resuelva este problema sin cambiar demasiado la syntax de statement de enums. Llegué a una solución que usa el preprocesador para recuperar una cadena de una statement enumerada en cadena.

Puedo definir enumeraciones no dispersas como esta:

 SMART_ENUM(State, enum State { RUNNING, SLEEPING, FAULT, UNKNOWN }) 

Y puedo interactuar con ellos de diferentes maneras:

 // With a stringstream std::stringstream ss; ss << State::FAULT; std::string myEnumStr = ss.str(); //Directly to stdout std::cout << State::FAULT << std::endl; //to a string std::string myStr = State::to_string(State::FAULT); //from a string State::State myEnumVal = State::from_string(State::FAULT); 

Basado en las siguientes definiciones:

 #define SMART_ENUM(enumTypeArg, ...) \ namespace enumTypeArg { \ __VA_ARGS__; \ std::ostream& operator<<(std::ostream& os, const enumTypeArg& val) { \ os << swissarmyknife::enums::to_string(#__VA_ARGS__, val); \ return os; \ } \ \ std::string to_string(const enumTypeArg& val) { \ return swissarmyknife::enums::to_string(#__VA_ARGS__, val); \ } \ \ enumTypeArg from_string(const std::string &str) { \ return swissarmyknife::enums::from_string(#__VA_ARGS__, str); \ } \ } \ namespace swissarmyknife { namespace enums { static inline std::string to_string(const std::string completeEnumDeclaration, size_t enumVal) throw (std::runtime_error) { size_t begin = completeEnumDeclaration.find_first_of('{'); size_t end = completeEnumDeclaration.find_last_of('}'); const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end ); size_t count = 0; size_t found = 0; do { found = identifiers.find_first_of(",}", found+1); if (enumVal == count) { std::string identifiersSubset = identifiers.substr(0, found); size_t beginId = identifiersSubset.find_last_of("{,"); identifiersSubset = identifiersSubset.substr(beginId+1); boost::algorithm::trim(identifiersSubset); return identifiersSubset; } ++count; } while (found != std::string::npos); throw std::runtime_error("The enum declaration provided doesn't contains this state."); } template  static inline EnumType from_string(const std::string completeEnumDeclaration, const std::string &enumStr) throw (std::runtime_error) { size_t begin = completeEnumDeclaration.find_first_of('{'); size_t end = completeEnumDeclaration.find_last_of('}'); const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end ); size_t count = 0; size_t found = 0; do { found = identifiers.find_first_of(",}", found+1); std::string identifiersSubset = identifiers.substr(0, found); size_t beginId = identifiersSubset.find_last_of("{,"); identifiersSubset = identifiersSubset.substr(beginId+1); boost::algorithm::trim(identifiersSubset); if (identifiersSubset == enumStr) { return static_cast(count); } ++count; } while (found != std::string::npos); throw std::runtime_error("No valid enum value for the provided string"); } }} 

Cuando necesitaré soporte para enum disperso y cuando tenga más tiempo mejoraré las implementaciones to_string y from_string con boost :: xpressive, pero esto costará en tiempo de comstackción debido a la importante plantilla realizada y el ejecutable generado es es probable que sea realmente más grande. Pero esto tiene la ventaja de que será más legible y mantenible que este feo código de manipulación manual de cadenas. :RE

De lo contrario, siempre utilicé boost :: bimap para realizar tales correspondencias entre valor de enum y cadena, pero debe mantenerse manualmente.