C ++: imprime el valor de enumeración como texto


89

Si tengo una enumeración como esta

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

Entonces quiero imprimir en 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 tu solución para esto?


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


Respuestas:


63

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

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

Usando interruptor / caso:

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

12
-1. Simplemente haga un cambio de caso en lugar de usar un mapa hash. El aumento de la complejidad no es bueno.
Simon

8
Buen punto. La próxima vez lo haré :) Pero ahora veo que ya ha editado su 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 de enum sin el nombre de la clase?
rh0x

2
#pes el preprocesador que encadena p. Así que llamar a PROCESS_VAL(ErrorA)la salida: case(ErrorA): s = "ErrorA"; break;.
Nashenas

No lo considero una solución óptima: Razones: 1) Tengo que mantener el doble de los enumvalores que creo que es un NO-GO . 2) Cuando entiendo la solución correctamente, solo funciona para una enum.
Peter VARGA

28

Use una matriz o vector de cadenas con valores coincidentes:

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

cout << ErrorTypes[anError];

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

Para probarlo aún más en el caso de que enum no comience desde 0, use:

cout << ErrorTypes[anError - ErrorA];

4
desafortunadamente, enum nos permite asignar valores a los elementos. ¿Cómo aborda el trabajo si tiene enumeraciones no contiguos, línea 'enum Status {OK = 0, Fail = -1, OutOfMemory = -2, IOError = -1000, ConversionError = -2000} `(para que luego pueda agregar IOErrors al rango -1001-1999)
nórdica

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

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 los valores de enumeración negativos?
Nordic Mainframe

2
@Luther: Deberá mantener los ErrorTypes actualizados. Una vez más, existe una compensación entre la simplicidad y la universalidad, depende de lo que sea más importante para el usuario. ¿Cuál es el problema con los valores de enumeración negativos?
Igor Oks

1
¿No debería esta matriz ser estática para la eficiencia de la memoria? y constante por seguridad?
Jonathan

15

Aquí hay un ejemplo basado en Boost.Preprocessor:

#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 depende de una herramienta externa, como la respuesta lua anterior, pero es C ++ puro, sigue el principio DRY y la sintaxis del usuario es legible (si se formatea correctamente. Por cierto, no necesita las barras invertidas cuando se usa DEFINE_ENUM, que parece un poco más natural, en mi opinión)
Fabio Fracassi

3
@Fabio Fracassi: "Esta solución no depende de una herramienta externa" Boost es una herramienta externa, una biblioteca C ++ no estándar. Además, es demasiado largo. La solución a un problema debe ser lo más simple posible. Este 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. por lo que esta es en realidad la solución más corta presentada aquí. Y para que el impulso sea externo, sí, pero menos que un script fuera del idioma para preprocesar partes de la fuente como lo es el script lua anterior. Además, boost está tan cerca del estándar que debería estar en todas las cajas de herramientas de los programadores de C ++. En mi humilde opinión, por supuesto
Fabio Fracassi

[Eliminé el escape innecesario de nuevas líneas en la invocación de macros. No son necesarios: una macro invocación puede abarcar varias líneas.]
James McNellis

La macro DEFINE_ENUMme da el error multiple definition of `format_ProgramStatus(ProgramStatus)'cuando intento usarla.
Hola

6

Puede usar un truco de preprocesador más simple si está dispuesto a listar sus enumentradas 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, trata el archivo como un archivo de inclusión, pero define lo que desea ERROR_DEFque haga.

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 usa alguna herramienta de exploración de fuentes (como cscope), tendrá que informarle sobre el archivo externo.


4

Ha habido una discusión aquí que podría ayudar: ¿Existe una forma sencilla de convertir la enumeración de C ++ en una cadena?

ACTUALIZACIÓN: Aquí # un script para Lua que crea un operador << para cada enumeración nombrada que encuentra. Esto podría necesitar algo de trabajo para que funcione en 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 probablemente sea un comienzo para ti.

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


¿No es una costumbre aquí comentar un '-1' para darle al autor la oportunidad de corregir su respuesta? Solo preguntando ...
Mainframe nórdico

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

4
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 (que suele dar mensajes de error inutilizables) y solo de usabilidad limitada (lua / python / perl puede generar código de arbitrario datos externos). Agrega un impulso a su lista de dependencias, que puede que ni siquiera esté permitido debido a la política del proyecto. Además, es invasivo porque requiere que defina sus enumeraciones en un DSL. Su herramienta de código fuente favorita o IDE podría tener problemas con eso. Y por último, pero no menos importante: no se puede establecer un límite en la expansión.
Mainframe nórdico

4

Utilizo una matriz de cadenas cada vez que defino una enumeración:

Perfil.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;

4

Esta es una buena manera

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

Imprimirlo con una matriz de matrices de caracteres

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

Me gusta esto

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

2
¿Qué pasa si mi enumeración comienza en 2000? Esta solución no funcionará.
Sitesh

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

2

Podría usar un contenedor de mapas stl ...

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 eso switch(n) { case XXX: return "XXX"; ... }? ¿Cuál tiene búsqueda O (1) y no necesita inicializarse? ¿O las enumeraciones cambian de alguna manera durante el tiempo de ejecución?
Mainframe nórdico

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

1
Bueno, es posible que desee generar "Este mi querido amigo Lutero es el Error A o" Este mi querido amigo Adrian es el Error B ". Además, el uso del mapa elimina la dependencia de las firmas de iostream, de modo que puede usarlo en cualquier otro lugar del código con concatenación de cadenas, por ejemplo, cadena x = "Hola" + m [ErrorA], etc.
Adrian Regan

Estoy seguro de que std :: map contiene muchos if's y switches. Leería esto como '¿cómo puedo hacer esto sin tener que escribir if's y switches?'
Nordic Mainframe

Estoy seguro de que sí, pero ciertamente no requiere que escriba un guión en Lua para resolver el problema ...
Adrian Regan

1

Para este problema, hago una función de ayuda como esta:

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 suele ser más eficiente que std::mappara colecciones pequeñas como esta.


1

Esta solución no requiere que use ninguna estructura de datos ni cree un archivo diferente.

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

enlace de 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

Lo bueno de hacerlo de esta manera es que también puede especificar sus propios 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 le gusta que sus códigos de error / descripciones sean muy descriptivos, es posible que no los desee en las compilaciones de producción. Desactivarlos para que solo se imprima 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, encontrar el número de error 525 sería un PITA. Podemos especificar manualmente los números en la enumeración inicial de esta 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

0

¿Qué tal esto?

    enum class ErrorCodes : int{
          InvalidInput = 0
    };

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

etc ... Sé que este es un ejemplo muy elaborado, pero creo que tiene una aplicación donde corresponde y es necesario y ciertamente es más corto que escribir un script para él.


0

Utilice 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: - aún es fácil de entender, sin embargo - permite varias visitas (no solo cadenas)

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


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.