with macro language how functions define c++ c preprocessor ansi-c

c++ - language - how to define macro in c



¿Cómo convertir una variable de tipo enum a una cadena? (28)

¿Cómo hacer que printf muestre los valores de las variables que son de tipo enum? Por ejemplo:

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

y lo que necesito es algo como

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

que debe mostrar una cadena "Linux", no un número entero.

Supongo que primero tengo que crear una matriz de cadenas indexada en valores. Pero no sé si esa es la forma más hermosa de hacerlo. ¿Es posible?


¿Has probado esto?

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

La macro stringify() se puede usar para convertir cualquier texto de su código en una cadena, pero solo el texto exacto entre paréntesis. No hay eliminación de referencias variables o sustituciones de macros o cualquier otro tipo de cosa hecha.

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


Aquí está el método de Old Skool (solía usarse ampliamente en gcc) usando solo el preprocesador de C. Es útil si está generando estructuras de datos discretas, pero necesita mantener el orden coherente entre ellas. Las entradas en mylist.tbl pueden por supuesto extenderse a algo mucho más complejo.

test.cpp:

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

Y luego mylist.tbl:

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


Aquí está mi código C ++:

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


Aquí hay muchas buenas respuestas, pero pensé que algunas personas podrían considerar que las mías son útiles. Me gusta porque la interfaz que utiliza para definir la macro es tan simple como puede ser. También es útil porque no tiene que incluir ninguna biblioteca adicional; todo viene con C ++ y ni siquiera requiere una versión realmente tardía. Recogí piezas de varios lugares en línea, así que no me puedo atribuir todo el mérito, pero creo que es lo suficientemente único como para justificar una nueva respuesta.

Primero crea un archivo de encabezado ... llámalo EnumMacros.h o algo así, y ponlo aquí:

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

Entonces, en tu programa principal puedes hacer esto ...

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

Donde el resultado sería >> El valor de ''Apple'' es: 2 de 4

¡Disfrutar!


El problema con C enums es que no es un tipo propio, como en C ++. Una enumeración en C es una forma de asignar identificadores a valores integrales. Solo eso. Es por eso que un valor enum es intercambiable con valores enteros.

Como usted adivina correctamente, una buena forma es crear un mapeo entre el valor enum y una cadena. Por ejemplo:

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


En c ++ como este:

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


Este es el bloque preprocesador

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

Definición de Enum

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

Llamada usando

GetStringOs_type(winblows);

Tomado de here . Cuan genial es eso ? :)


Este simple ejemplo funcionó para mí. Espero que esto ayude.

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


Gracias James por tu sugerencia. Fue muy útil, así que lo implementé al revés para contribuir de alguna manera.

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


He combinado las soluciones de James'' , Howard''s y Éder''s y he creado una implementación más genérica:

  • El valor int y la representación de cadena personalizada se pueden definir opcionalmente para cada elemento enum
  • "clase enum" se usa

El código completo está escrito a continuación (use "DEFINE_ENUM_CLASS_WITH_ToString_METHOD" para definir una enumeración) ( demostración en línea ).

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


La solución ingenua, por supuesto, es escribir una función para cada enumeración que realiza la conversión a cadena:

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

Esto, sin embargo, es un desastre de mantenimiento. Con la ayuda de la biblioteca Boost.Preprocessor, que se puede utilizar con códigos C y C ++, puede aprovechar fácilmente el preprocesador y dejar que genere esta función por usted. La macro de generación es la siguiente:

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

La primera macro (que comienza con X_ ) se usa internamente por la segunda. La segunda macro primero genera la enumeración, luego genera una función ToString que toma un objeto de ese tipo y devuelve el nombre del enumerador como una cadena (esta implementación, por razones obvias, requiere que los enumeradores se asignen a valores únicos).

En C ++, podría implementar la función ToString como una sobrecarga del operator<< lugar, pero creo que es un poco más limpio requerir un " ToString " explícito para convertir el valor a la forma de cadena.

Como ejemplo de uso, su enumeración de tipo OS_type se definiría de la siguiente manera:

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

Si bien la macro al principio parece que es mucho trabajo, y la definición de OS_type parece bastante extraña, recuerde que debe escribir la macro una vez, luego puede usarla para cada enumeración. Puede agregar funcionalidad adicional (por ejemplo, una conversión de cadena a conversión) sin demasiados problemas, y resuelve completamente el problema de mantenimiento, ya que solo debe proporcionar los nombres una vez, cuando invoca la macro.

La enumeración se puede usar como si se hubiera definido normalmente:

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

Los fragmentos de código en esta publicación, que comienzan con la línea #include <boost/preprocessor.hpp> , se pueden compilar como publicados para mostrar la solución.

Esta solución particular es para C ++ ya que usa sintaxis específica de C ++ (por ej., No typedef enum ) y sobrecarga de funciones, pero sería sencillo hacerlo también con C.


Mi propia preferencia es minimizar las macros repetitivas y las macros difíciles de entender y evitar introducir las definiciones de macro en el espacio general del compilador.

Entonces, en el archivo de encabezado:

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

y la implementación de cpp es:

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

Tenga en cuenta el #undef de la macro tan pronto terminemos con ella.


Mi solución, no usar boost:

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

Y aquí está cómo usarlo

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


Otra tarde para la fiesta, usando el preprocesador:

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

(Acabo de poner los números de las líneas para que sea más fácil hablar de ellas). Las líneas 1 a 4 son las que edita para definir los elementos de la enumeración. (Lo he llamado una "macro lista", porque es una macro que hace una lista de cosas. @Lundin me informa que esta es una técnica conocida llamada X-macros).

La línea 7 define la macro interna para completar la declaración enum real en las líneas 8-11. La línea 12 no define la macro interna (solo para silenciar la advertencia del compilador).

La línea 14 define la macro interna para crear una versión de cadena del nombre del elemento enum. Luego, las líneas 15-18 generan una matriz que puede convertir un valor enum a la cadena correspondiente.

Las líneas 21-27 generan una función que convierte una cadena en el valor enum o devuelve NULL si la cadena no coincide con ninguna.

Esto es un poco engorroso en la forma en que maneja el elemento 0º. De hecho, he trabajado en eso en el pasado.

Admito que esta técnica molesta a las personas que no quieren pensar que el preprocesador mismo pueda programarse para escribir código para usted. Creo que ilustra fuertemente la diferencia entre legibilidad y mantenibilidad . El código es difícil de leer, pero si la enumeración tiene unos pocos cientos de elementos, puede agregar, eliminar o reorganizar elementos y aún así asegurarse de que el código generado no tenga errores.


Para C99 hay P99_DECLARE_ENUM en P99 que le permite simplemente declarar enum esta manera:

P99_DECLARE_ENUM(color, red, green, blue);

y luego use color_getname(A) para obtener una cadena con el nombre del color.


Para extender la respuesta de James, alguien quiere un código de ejemplo para admitir enum define con int value, también tengo este requisito, así que esta es mi manera de hacerlo:

La primera es la macro de uso interno, que FOR_EACH usa:

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

Y, aquí está la macro de definición:

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

Entonces, cuando lo use, puede escribir de esta manera:

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

que se expandirá a:

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

La idea básica es definir un SEQ, que cada elemento es un TUPLE, por lo que podemos ponerle un valor de adición al miembro enum. En el ciclo FOR_EACH, compruebe el tamaño de TUPLE del elemento, si el tamaño es 2, expanda el código a KEY = VALUE, de lo contrario, conserve el primer elemento de TUPLE.

Because the input SEQ is actually TUPLEs, so if you want to define STRINGIZE functions, you may need to pre-process the input enumerators first, here is the macro to do the job:

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

The macro DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ will only keep the first element in every TUPLE, and later convert to SEQ, now modify James'' code, you will have the full power.

My implementation maybe not the simplest one, so if you do not find any clean code, mine for your reference.


Realmente no hay una forma hermosa de hacer esto. Simplemente configure una serie de cadenas indexadas por la enumeración.

Si realiza una gran cantidad de salida, puede definir un operador << que tome un parámetro enum y haga la búsqueda por usted.


Suponiendo que su enumeración ya está definida, puede crear una matriz de pares:

std::pair<QTask::TASK, QString> pairs [] = { std::pair<OS_type, string>(Linux, "Linux"), std::pair<OS_type, string>(Windows, "Windows"), std::pair<OS_type, string>(Apple, "Apple"), };

Ahora puedes crear un mapa:

std::map<OS_type, std::string> stdmap(pairs, pairs + sizeof(pairs) / sizeof(pairs[0]));

Ahora, puedes usar el mapa. Si cambiaste tu enumeración, debes agregar / eliminar pares de pares de arreglos []. Creo que es la forma más elegante de obtener una cadena de enum en C ++.


Un poco tarde para la fiesta, pero esta es mi solución C ++ 11:

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


Utilice std::map<OS_type, std::string> y std::map<OS_type, std::string> con enum como clave, y la representación de cadenas como valores, luego puede hacer esto:

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


A clean solution to this problem would be:

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

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


Clean, safe solution in pure standard C:

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

Salida

0 hello 1 world 1 world

Rationale

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

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

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

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

Explanation of the code

Suppose we have an enum like

typedef enum { hello, world } test_t;

This can be changed to

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

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

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

Y eso es. By using hello , we get the enum constant with value 0. By using test_str[hello] we get the string "hello".

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

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

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

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

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

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

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

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

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

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


Es 2017 pero la pregunta sigue viva

Otra forma más:

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

Productos:

Error: [0]NO_ERROR, OK Error: [1]FILE_NOT_FOUND, Not found Error: [2]LABEL_UNINITIALISED, Uninitialized usage


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

The header file "my_enum.hpp" is:

#include <cstring> namespace ENUM_HELPERS{ int replace_commas_and_spaces_with_null(char* string){ int i, N; N = strlen(string); for(i=0; i<N; ++i){ if( isspace(string[i]) || string[i] == '',''){ string[i]=''/0''; } } return(N); } int count_words_null_delim(char* string, int tot_N){ int i; int j=0; char last = ''/0''; for(i=0;i<tot_N;++i){ if((last == ''/0'') && (string[i]!=''/0'')){ ++j; } last = string[i]; } return(j); } int get_null_word_offsets(char* string, int tot_N, int current_w){ int i; int j=0; char last = ''/0''; for(i=0; i<tot_N; ++i){ if((last==''/0'') && (string[i]!=''/0'')){ if(j == current_w){ return(i); } ++j; } last = string[i]; } return(tot_N); //null value for offset } int find_offsets(int* offsets, char* string, int tot_N, int N_words){ int i; for(i=0; i<N_words; ++i){ offsets[i] = get_null_word_offsets(string, tot_N, i); } return(0); } } #define MAKE_ENUM(NAME, ...) / namespace NAME{ / enum ENUM {__VA_ARGS__}; / char name_holder[] = #__VA_ARGS__; / int name_holder_N = / ENUM_HELPERS::replace_commas_and_spaces_with_null(name_holder); / int N = / ENUM_HELPERS::count_words_null_delim( / name_holder, name_holder_N); / int offsets[] = {__VA_ARGS__}; / int ZERO = / ENUM_HELPERS::find_offsets( / offsets, name_holder, name_holder_N, N); / char* tostring(int i){ / return(&name_holder[offsets[i]]); / } / }

Ejemplo de uso:

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

Esto dará como resultado:

MERCURY EARTH

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

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


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

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

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

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

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

Now defining an enum type and have strings for it becomes really easy. All you need to do is:

ENUM(MyEnumType, A, B, C);

The following lines can be used to test it.

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

Esto dará como resultado:

3 A B C A B C A B C

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

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

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

  • Fácil de usar.
  • No string splitting at runtime required.
  • Separate strings are available at compile time.
  • Fácil de leer. The first set of macros may need an extra second, but aren''t really that complicated.

#include <EnumString.h>

desde http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C y después

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

insertar

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 la enumeración no son duplicados.

Código de muestra para convertir un valor enum a cadena:

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

Código de muestra para todo lo contrario:

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


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

Esta es una versión enum extendida de clase elaborada ... no agrega ningún otro valor enum distinto a los provistos.

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


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