2010-07-27 21 views
55

Si tengo una enumeración como estoC++: Imprima valor de enumeración como texto

enum Errors 
{ErrorA=0, ErrorB, ErrorC}; 

entonces quiero imprimir a la consola

Errors anError = ErrorA; 
cout<<anError;/// 0 will be printed 

pero lo que quiero es el texto "ErrorA" ¿Puedo hacerlo sin usar if/switch?
¿Y cuál es su solución para esto?

+0

Creo que mi respuesta bastante buena, ¿le importaría echar un vistazo? – Xiao

+1

Para C++ 11 'enum class': http://stackoverflow.com/questions/11421432/how-can-i-output-the-value-of-an-enum-class-in-c11 –

+0

Enum to string : http://stackoverflow.com/questions/201593/is-there-a-simple-way-to-convert-c-enum-to-string –

Respuesta

45

Uso de mapa:

#include <iostream> 
#include <map> 
#include <string> 

enum Errors {ErrorA=0, ErrorB, ErrorC}; 

std::ostream& operator<<(std::ostream& out, const Errors value){ 
    static std::map<Errors, std::string> strings; 
    if (strings.size() == 0){ 
#define INSERT_ELEMENT(p) strings[p] = #p 
     INSERT_ELEMENT(ErrorA);  
     INSERT_ELEMENT(ErrorB);  
     INSERT_ELEMENT(ErrorC);    
#undef INSERT_ELEMENT 
    } 

    return out << strings[value]; 
} 

int main(int argc, char** argv){ 
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl; 
    return 0; 
} 

Uso de matriz de estructuras con búsqueda lineal:

#include <iostream> 
#include <string> 

enum Errors {ErrorA=0, ErrorB, ErrorC}; 

std::ostream& operator<<(std::ostream& out, const Errors value){ 
#define MAPENTRY(p) {p, #p} 
    const struct MapEntry{ 
     Errors value; 
     const char* str; 
    } entries[] = { 
     MAPENTRY(ErrorA), 
     MAPENTRY(ErrorB), 
     MAPENTRY(ErrorC), 
     {ErrorA, 0}//doesn't matter what is used instead of ErrorA here... 
    }; 
#undef MAPENTRY 
    const char* s = 0; 
    for (const MapEntry* i = entries; i->str; i++){ 
     if (i->value == value){ 
      s = i->str; 
      break; 
     } 
    } 

    return out << s; 
} 

int main(int argc, char** argv){ 
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl; 
    return 0; 
} 

El uso de switch/case:

#include <iostream> 
#include <string> 

enum Errors {ErrorA=0, ErrorB, ErrorC}; 

std::ostream& operator<<(std::ostream& out, const Errors value){ 
    const char* s = 0; 
#define PROCESS_VAL(p) case(p): s = #p; break; 
    switch(value){ 
     PROCESS_VAL(ErrorA);  
     PROCESS_VAL(ErrorB);  
     PROCESS_VAL(ErrorC); 
    } 
#undef PROCESS_VAL 

    return out << s; 
} 

int main(int argc, char** argv){ 
    std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl; 
    return 0; 
} 
+8

-1. Simplemente haga una caja de interruptores en lugar de usar un hash-map. El aumento de la complejidad no es algo bueno. – Simon

+8

Buen punto. La próxima vez lo haré :) Pero ahora veo que ya has editado tu publicación para agregar el tipo de funcionalidad que estaba buscando. ¡Buen trabajo! – Simon

+1

¿qué es #p? si en el tercer ejemplo en lugar de enum utilizo una clase enum, ¿es posible obtener solo la cadena enum sin el nombre de la clase? – rh0x

21

utilizar una matriz o vector de cadenas con valores para los productos:

char *ErrorTypes[] = 
{ 
    "errorA", 
    "errorB", 
    "errorC" 
}; 

cout << ErrorTypes[anError]; 

EDIT: La solución anterior es aplicable cuando el enum es contigua, es decir, comienza desde 0 y no hay valores asignados. Funcionará perfectamente con la enumeración en la pregunta.

Para más que una prueba para el caso de que la enumeración no se inicia desde 0, utilice:

cout << ErrorTypes[anError - ErrorA]; 
+3

desafortunadamente, enum nos permite asignar valores a los elementos. ¿Cómo se acerca el trabajo si tiene enumeraciones no contiguas, línea 'enum Estado {OK = 0, Fail = -1, OutOfMemory = -2, IOError = -1000, ConversionError = -2000} '(para que luego pueda agregar IOErrors en el rango -1001-1999) –

+0

@Luther: Sí, esto solo funcionará con enumeraciones contiguas, ** que la mayoría de las enumeraciones son **. En caso de que la enumeración no sea contigua, deberá usar otro enfoque, es decir, mapas. Pero en el caso de la enumeración contigua, sugeriría utilizar este enfoque y no complicar demasiado. –

+2

Entonces, si mi colega agrega NewValue a una enumeración y no actualiza la matriz ErrorTypes, entonces ErrorTypes [NewValue] produce qué? ¿Y cómo manejo valores de enum negativos? –

2

Se puede usar un contenedor STL mapa ....

typedef map<Errors, string> ErrorMap; 

ErrorMap m; 
m.insert(ErrorMap::value_type(ErrorA, "ErrorA")); 
m.insert(ErrorMap::value_type(ErrorB, "ErrorB")); 
m.insert(ErrorMap::value_type(ErrorC, "ErrorC")); 

Errors error = ErrorA; 

cout << m[error] << endl; 
+4

¿Cómo es este un mapa mejor que 'cambiar (n) {caso XXX: devolver" XXX "; ...} '? ¿Qué tiene O (1) búsqueda y no necesita ser inicializado? ¿O las enumeraciones cambian de alguna manera durante el tiempo de ejecución? –

+0

Estoy de acuerdo con @Luther Blissett en el uso de la declaración de conmutación (o un puntero a función también) – KedarX

+1

Bueno, él puede querer producir "Este mi querido amigo Luther es el Error A" o "Este mi querido amigo Adrian es el Error B." También, usando map elimina la dependencia de las firmas iostream, de forma que puede usarlo en cualquier otro lugar del código con concatenación de cadenas, por ejemplo, string x = "Hello" + m [ErrorA], etc. –

4

Ha habido una discusión aquí lo que podría ayudar: Is there a simple way to convert C++ enum to string?

ACTUALIZACIÓN: aquí # script Lua SA por el que se crea un artículo erator < < por cada enum llamada que encuentra. Esto podría necesitar algo de trabajo para hacer que funcione para los casos menos simples [1]:

function make_enum_printers(s) 
    for n,body in string.gmatch(s,'enum%s+([%w_]+)%s*(%b{})') do 
    print('ostream& operator<<(ostream &o,'..n..' n) { switch(n){') 
    for k in string.gmatch(body,"([%w_]+)[^,]*") do 
    print(' case '..k..': return o<<"'..k..'";') 
    end 
    print(' default: return o<<"(invalid value)"; }}') 
    end 
end 

local f=io.open(arg[1],"r") 
local s=f:read('*a') 
make_enum_printers(s) 

Dada esta entrada:

enum Errors 
{ErrorA=0, ErrorB, ErrorC}; 

enum Sec { 
    X=1,Y=X,foo_bar=X+1,Z 
}; 

Produce:

ostream& operator<<(ostream &o,Errors n) { switch(n){ 
    case ErrorA: return o<<"ErrorA"; 
    case ErrorB: return o<<"ErrorB"; 
    case ErrorC: return o<<"ErrorC"; 
    default: return o<<"(invalid value)"; }} 
ostream& operator<<(ostream &o,Sec n) { switch(n){ 
    case X: return o<<"X"; 
    case Y: return o<<"Y"; 
    case foo_bar: return o<<"foo_bar"; 
    case Z: return o<<"Z"; 
    default: return o<<"(invalid value)"; }} 

Así que eso es probablemente una comenzar por ti

[1] enumeraciones en diferentes o no de espacio de nombres ámbitos, enumeraciones con expresiones inicializador que contienen un komma, etc.

+0

¿No es una costumbre aquí comentar un '-1' para darle al póster la oportunidad de corregir su respuesta? Simplemente preguntando ... –

+2

Creo que la solución Boost PP a continuación (de Philip) es mejor, porque el uso de herramientas externas es muy costoso para el mantenimiento. pero no -1 porque la respuesta es válida de otra manera –

+3

El Boost PP * también * es un problema de mantenimiento, porque necesita que todos hablen el metalenguaje de Boost PP, que es * terrible *, fácil de romper (dando mensajes de error generalmente inutilizables) y solo de usabilidad limitada (lua/python/perl puede generar código a partir de datos externos arbitrarios). Agrega impulso a su lista de dependencias, que ni siquiera puede permitirse debido a la política del proyecto. Además, es invasivo porque requiere que definas tus enumeraciones en una DSL. Su herramienta favorita de código fuente o IDE podría tener problemas con eso. Y por último pero no menos importante: no puede establecer un punto de interrupción en la expansión. –

1

Para este problema, que hacen una función de ayuda de esta manera:

const char* name(Id id) { 
    struct Entry { 
     Id id; 
     const char* name; 
    }; 
    static const Entry entries[] = { 
     { ErrorA, "ErrorA" }, 
     { ErrorB, "ErrorB" }, 
     { 0, 0 } 
    } 
    for (int it = 0; it < gui::SiCount; ++it) { 
     if (entries[it].id == id) { 
      return entries[it].name; 
     } 
    } 
    return 0; 
} 

La búsqueda lineal es generalmente más eficiente que std::map para las pequeñas colecciones de este tipo.

12

Aquí hay un ejemplo basado en Boost.Preprocesador:

#include <iostream> 

#include <boost/preprocessor/punctuation/comma.hpp> 
#include <boost/preprocessor/control/iif.hpp> 
#include <boost/preprocessor/comparison/equal.hpp> 
#include <boost/preprocessor/stringize.hpp> 
#include <boost/preprocessor/seq/for_each.hpp> 
#include <boost/preprocessor/seq/size.hpp> 
#include <boost/preprocessor/seq/seq.hpp> 


#define DEFINE_ENUM(name, values)        \ 
    enum name {             \ 
    BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_VALUE, , values)   \ 
    };               \ 
    inline const char* format_##name(name val) {     \ 
    switch (val) {            \ 
     BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_FORMAT, , values)  \ 
    default:             \ 
     return 0;            \ 
    }               \ 
    } 

#define DEFINE_ENUM_VALUE(r, data, elem)      \ 
    BOOST_PP_SEQ_HEAD(elem)          \ 
    BOOST_PP_IIF(BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE(elem), 2),  \ 
       = BOOST_PP_SEQ_TAIL(elem),)      \ 
    BOOST_PP_COMMA() 

#define DEFINE_ENUM_FORMAT(r, data, elem)    \ 
    case BOOST_PP_SEQ_HEAD(elem):      \ 
    return BOOST_PP_STRINGIZE(BOOST_PP_SEQ_HEAD(elem)); 


DEFINE_ENUM(Errors, 
      ((ErrorA)(0)) 
      ((ErrorB)) 
      ((ErrorC))) 

int main() { 
    std::cout << format_Errors(ErrorB) << std::endl; 
} 
+2

+1, esta solución no se basa en una herramienta externa, como la respuesta lua anterior, pero es pura C++, sigue el principio DRY y la sintaxis del usuario es legible (si está formateada correctamente. Por cierto, no necesita el Barras invertidas al usar DEFINE_ENUM, que se ve un poco más natural, IMO) –

+3

@Fabio Fracassi: "Esta solución no depende de una herramienta externa" Boost es una herramienta externa: biblioteca C++ no estándar. Además, es un poco demasiado largo. La solución a un problema debe ser lo más simple posible. Éste no califica ... – SigTerm

+2

En realidad, es todo lo que podría poner la mayor parte del código (de hecho, todo excepto la definición real) se puede poner en un solo encabezado. así que esta es en realidad la solución más breve presentada aquí. Y para que el impulso sea externo, sí, pero menos que un guión fuera del idioma para preprocesar partes de la fuente como lo está el guión lua anterior. Además, el impulso está tan cerca del estándar que debería estar en cada caja de herramientas de los programadores de C++. Sólo en mi humilde opinión, por supuesto –

2

que utilizan una matriz de cadenas siempre defino una enumeración:

Profile.h

#pragma once 

struct Profile 
{ 
    enum Value 
    { 
     Profile1, 
     Profile2, 
    }; 

    struct StringValueImplementation 
    { 
     const wchar_t* operator[](const Profile::Value profile) 
     { 
      switch (profile) 
      { 
      case Profile::Profile1: return L"Profile1"; 
      case Profile::Profile2: return L"Profile2"; 
      default: ASSERT(false); return NULL; 
      } 
     } 
    }; 

    static StringValueImplementation StringValue; 
}; 

Profile.cpp

#include "Profile.h" 

Profile::StringValueImplementation Profile::StringValue; 
3

Se puede utilizar una simple pre -processor truco si está dispuesto a enumerar sus entradas enum en un archivo externo.

/* file: errors.def */ 
/* syntax: ERROR_DEF(name, value) */ 
ERROR_DEF(ErrorA, 0x1) 
ERROR_DEF(ErrorB, 0x2) 
ERROR_DEF(ErrorC, 0x4) 

Luego, en un archivo de origen, que tratan el archivo como un archivo de inclusión, pero que definen lo que quiere el ERROR_DEF que hacer.

enum Errors { 
#define ERROR_DEF(x,y) x = y, 
#include "errors.def" 
#undef ERROR_DEF 
}; 

static inline std::ostream & operator << (std::ostream &o, Errors e) { 
    switch (e) { 
    #define ERROR_DEF(x,y) case y: return o << #x"[" << y << "]"; 
    #include "errors.def" 
    #undef ERROR_DEF 
    default: return o << "unknown[" << e << "]"; 
    } 
} 

Si utiliza alguna herramienta de exploración de fuente (como cscope), que tendrá que dejar que se sabe sobre el archivo externo.

0

¿Qué tal esto?

enum class ErrorCodes : int{ 
      InvalidInput = 0 
    }; 

    std::cout << ((int)error == 0 ? "InvalidInput" : "") << std::endl; 

etc ... Sé que esto es un ejemplo muy artificial, pero creo que tiene aplicación cuando proceda y sea necesario y sin duda es más corto que escribir un guión para él.

2
#include <iostream> 
using std::cout; 
using std::endl; 

enum TEnum 
{ 
    EOne, 
    ETwo, 
    EThree, 
    ELast 
}; 

#define VAR_NAME_HELPER(name) #name 
#define VAR_NAME(x) VAR_NAME_HELPER(x) 

#define CHECK_STATE_STR(x) case(x):return VAR_NAME(x); 

const char *State2Str(const TEnum state) 
{ 
    switch(state) 
    { 
    CHECK_STATE_STR(EOne); 
    CHECK_STATE_STR(ETwo); 
    CHECK_STATE_STR(EThree); 
    CHECK_STATE_STR(ELast); 
    default: 
     return "Invalid"; 
    } 
} 

int main() 
{ 
    int myInt=12345; 
    cout << VAR_NAME(EOne) " " << VAR_NAME(myInt) << endl; 

    for(int i = -1; i < 5; i) 
    cout << i << " " << State2Str((TEnum)i) << endl; 
    return 0; 
} 
1

Esta solución no requiere el uso de ninguna estructura de datos o crear un archivo diferente.

Básicamente, usted define todos sus valores enum en un #define, luego los usa en el operador < <. Muy similar a la respuesta de @jxh.

enlace Ideone para la iteración final: http://ideone.com/hQTKQp

código completo:

#include <iostream> 

#define ERROR_VALUES ERROR_VALUE(NO_ERROR)\ 
ERROR_VALUE(FILE_NOT_FOUND)\ 
ERROR_VALUE(LABEL_UNINITIALISED) 

enum class Error 
{ 
#define ERROR_VALUE(NAME) NAME, 
    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) case Error::NAME: return os << "[" << errVal << "]" #NAME; 
    ERROR_VALUES 
#undef ERROR_VALUE 
    default: 
     // If the error value isn't found (shouldn't happen) 
     return os << errVal; 
    } 
} 

int main() { 
    std::cout << "Error: " << Error::NO_ERROR << std::endl; 
    std::cout << "Error: " << Error::FILE_NOT_FOUND << std::endl; 
    std::cout << "Error: " << Error::LABEL_UNINITIALISED << std::endl; 
    return 0; 
} 

Salida:

Error: [0]NO_ERROR 
Error: [1]FILE_NOT_FOUND 
Error: [2]LABEL_UNINITIALISED 

Una cosa buena de hacerlo de esta manera es que también se puede especificar su propio mensajes personalizados para cada error si cree que los necesita:

#include <iostream> 

#define ERROR_VALUES ERROR_VALUE(NO_ERROR, "Everything is fine")\ 
ERROR_VALUE(FILE_NOT_FOUND, "File is not found")\ 
ERROR_VALUE(LABEL_UNINITIALISED, "A component tried to the label before it was initialised") 

enum class Error 
{ 
#define ERROR_VALUE(NAME,DESCR) NAME, 
    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,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR; 
    ERROR_VALUES 
#undef ERROR_VALUE 
    default: 
     return os << errVal; 
    } 
} 

int main() { 
    std::cout << "Error: " << Error::NO_ERROR << std::endl; 
    std::cout << "Error: " << Error::FILE_NOT_FOUND << std::endl; 
    std::cout << "Error: " << Error::LABEL_UNINITIALISED << std::endl; 
    return 0; 
} 

Salida:

Error: [0]NO_ERROR; Everything is fine 
Error: [1]FILE_NOT_FOUND; File is not found 
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised 

Si te gusta hacer sus códigos de error/descripciones muy descriptivo, puede que no desee en la producción crece. Apagarlos por lo que sólo se imprime el valor es fácil:

inline std::ostream& operator<<(std::ostream& os, Error err) 
{ 
    int errVal = static_cast<int>(err); 
    switch (err) 
    { 
    #ifndef PRODUCTION_BUILD // Don't print out names in production builds 
    #define ERROR_VALUE(NAME,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR; 
     ERROR_VALUES 
    #undef ERROR_VALUE 
    #endif 
    default: 
     return os << errVal; 
    } 
} 

Salida:

Error: 0 
Error: 1 
Error: 2 

Si este es el caso, la búsqueda de número de error 525 sería un PITA.Podemos especificar manualmente los números en la enumeración inicial de la siguiente manera:

#define ERROR_VALUES ERROR_VALUE(NO_ERROR, 0, "Everything is fine")\ 
ERROR_VALUE(FILE_NOT_FOUND, 1, "File is not found")\ 
ERROR_VALUE(LABEL_UNINITIALISED, 2, "A component tried to the label before it was initialised")\ 
ERROR_VALUE(UKNOWN_ERROR, -1, "Uh oh") 

enum class Error 
{ 
#define ERROR_VALUE(NAME,VALUE,DESCR) NAME=VALUE, 
    ERROR_VALUES 
#undef ERROR_VALUE 
}; 

inline std::ostream& operator<<(std::ostream& os, Error err) 
{ 
    int errVal = static_cast<int>(err); 
    switch (err) 
    { 
#ifndef PRODUCTION_BUILD // Don't print out names in production builds 
#define ERROR_VALUE(NAME,VALUE,DESCR) case Error::NAME: return os << "[" #VALUE "]" #NAME <<"; " << DESCR; 
    ERROR_VALUES 
#undef ERROR_VALUE 
#endif 
    default: 
     return os <<errVal; 
    } 
} 
    ERROR_VALUES 
#undef ERROR_VALUE 
#endif 
    default: 
    { 
     // If the error value isn't found (shouldn't happen) 
     return os << static_cast<int>(err); 
     break; 
    } 
    } 
} 

Salida:

Error: [0]NO_ERROR; Everything is fine 
Error: [1]FILE_NOT_FOUND; File is not found 
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised 
Error: [-1]UKNOWN_ERROR; Uh oh 
3

Esta es una buena manera,

enum Rank { ACE = 1, DEUCE, TREY, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING }; 

de impresión con una serie de matrices de caracteres

const char* rank_txt[] = {"Ace", "Deuce", "Trey", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Four", "King" } ; 

Me gusta th se

std::cout << rank_txt[m_rank - 1] 
+0

¿Qué sucede si mi enumeración comienza en el año 2000? Esta solución no funcionará. – Sitesh

0

utiliza el preprocesador:

#define VISIT_ERROR(FIRST, MIDDLE, LAST) \ 
    FIRST(ErrorA) MIDDLE(ErrorB) /* MIDDLE(ErrorB2) */ LAST(ErrorC) 

enum Errors 
{ 
    #define ENUMFIRST_ERROR(E) E=0, 
    #define ENUMMIDDLE_ERROR(E) E, 
    #define ENUMLAST_ERROR(E) E 
    VISIT_ERROR(ENUMFIRST_ERROR, ENUMMIDDLE_ERROR, ENUMLAST_ERROR) 
    // you might undefine the 3 macros defined above 
}; 

std::string toString(Error e) 
{ 
    switch(e) 
    { 
    #define CASERETURN_ERROR(E) case E: return #E; 
    VISIT_ERROR(CASERETURN_ERROR, CASERETURN_ERROR, CASERETURN_ERROR) 
    // you might undefine the above macro. 
    // note that this will produce compile-time error for synonyms in enum; 
    // handle those, if you have any, in a distinct macro 

    default: 
     throw my_favourite_exception(); 
    } 
} 

La ventaja de este enfoque es que: - Todavía es sencillo de entender, sin embargo, - que permite varias visitas (no sólo de cuerda)

Si está dispuesto a colocar el primero, cree una macro FOREACH(), luego #define ERROR_VALUES() (ErrorA, ErrorB, ErrorC) y escriba sus visitantes en términos de FOREACH(). Luego intenta pasar una revisión del código :).