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

128

¿Cómo hacer que printf muestre los valores de 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 indexadas en valores. Pero no sé si esa es la forma más hermosa de hacerlo. ¿Es posible en absoluto?

psihodelia
fuente
2
Elija: aquí , aquí , aquí . Es un desastre, todos son semi-duplicados.
rubenvb
Otro , este es más completo.
bit2shift
Posible duplicado de stackoverflow.com/questions/207976/… para C ++ y stackoverflow.com/questions/9907160/… para C
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Tantas respuestas aún sin "clase de enumeración"
Vivick

Respuestas:

70

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

Si realiza muchos resultados, puede definir un operador << que tome un parámetro enum y realice la búsqueda por usted.

Bo Persson
fuente
2
También puede comprobar en tiempo de compilación que su matriz tiene el número esperado de cadenas.
markh44
2
Sé que estoy en una gran minoría con esto, pero para los programadores como yo que no desean depender de bibliotecas de terceros masivas y / o código lleno de marcos para resolver las deficiencias nativas del lenguaje, creo que esto es La solución más simple y pura para el estándar de hoy en día. +1
Syndog
13
@Syndog Luego, este programador actualiza la enumeración larga de 56 enumeradores en su código de producción bajo mucha presión para liberar una característica vencida, y se olvida de actualizar esa matriz indexada por enumeración. Pasa desapercibido, ya que el código de depuración de la aplicación solo utiliza la función de impresión relacionada. 2 meses después, usted es el primero en ejecutar ese código de depuración: luego le da la información incorrecta, por lo que pierde medio día construyendo suposiciones basadas en esta información incorrecta, antes de darse cuenta de que primero tuvo que depurar el código de depuración: el El diseño se basa en la duplicación explícita.
Anuncio N
1
@AdN Ese diseño está mal. La asignación de enum a cadena legible por humanos no debe implementarse como una matriz de cadenas indexadas por el valor enum. Su experiencia (presumiblemente) muestra por qué. La asignación debe ser una matriz explícita de pares (enum, string), por lo que si olvida agregar una entrada para su nuevo valor de enumeración, obtendrá "???" como salida, pero al menos no arruinará los nombres de todas las otras enumeraciones.
Brewbuck
8
@AdN su escenario es la razón por la que prefiero una función que contenga un interruptor (sin cláusula predeterminada) en lugar de una matriz, y para configurar los interruptores del compilador en el archivo de compilación para emitir un error para un interruptor sobre una enumeración que no cubre todo valores posibles. Agregar una nueva entrada de enumeración sin actualizar las instrucciones de cambio relevantes provocará un error de compilación.
divegeek
131

La solución ingenua, por supuesto, es escribir una función para cada enumeración que realice 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 usar con código 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 (comenzando por X_) es utilizada internamente por la segunda. La segunda macro primero genera la enumeración, luego genera una ToStringfunción 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 asignen valores únicos).

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

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

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

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

La enumeración se puede usar como si se definiera 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, comenzando con la #include <boost/preprocessor.hpp>línea, se pueden compilar tal como se publicaron para demostrar la solución.

Esta solución particular es para C ++, ya que utiliza la sintaxis específica de C ++ (p. Ej., No typedef enum) y la sobrecarga de funciones, pero sería sencillo hacer que esto también funcione con C.

James McNellis
fuente
77
+1, la maquinaria de implementación da miedo, pero la interfaz final es difícil de superar por elegancia.
deft_code
44
¿Hay alguna forma de obtener esto que también le permita dar valores enteros enum. Por ejemplo, ¿Windows sería 3, Linux 5 y Apple 7?
Mark
44
Sí, puede cambiar (Windows)y (Windows, 3)luego reemplazar el BOOST_PP_SEQ_ENUMcon un escrito adecuadamente BOOST_PP_SEQ_FOR_EACH. No tengo un ejemplo tan útil, pero puedo escribir uno si lo desea.
James McNellis el
2
@JamesMcNellis Definitivamente me gustaría un ejemplo de un código que cumpla con lo que Mark pidió, ¿sería tan amable de mostrarnos el camino? :)
Omer Raviv
2
NOTA: el preprocesador boost tiene un límite estricto de 256 elementos. Para enumeraciones más grandes, se necesita una solución diferente.
dshin
32

Este es el bloque de 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 enumeración

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

Llamar usando

GetStringOs_type(winblows);

Tomado de aquí . Cuan genial es eso ? :)

Reno
fuente
1
Esta es la única solución que funciona cuando su enumeración tiene más de 256 elementos.
dshin
8

Úselo std::map<OS_type, std::string>y llénelo con enum como clave y la representación de cadena como valores, luego puede hacer esto:

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

El problema con las enumeraciones de C es que no es un tipo propio, como lo es 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 adivinas correctamente, una buena manera es crear una asignación entre el valor de enumeración y una cadena. Por ejemplo:

char * OS_type_label[] = {
    "Linux",
    "Apple",
    "Windows"
};
Andrés
fuente
Asumí, aparentemente de manera incorrecta, que el lenguaje de programación está restringido a C.
Andrew
1
estás un poco apagado, enum son tipos en C. Las constantes de tipo de enumeración integral son de tipo inty no del enumtipo a través del cual se definen, es quizás lo que quisiste decir. Pero no veo en absoluto lo que esto tiene que ver con la pregunta.
Jens Gustedt
7

He combinado el James , Howard y de Eder soluciones y ha creado una aplicación más genérica:

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

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;
}
Oso polar
fuente
Esta es la mejor respuesta hasta ahora
Arnout
6

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";
}
dgmz
fuente
13
No funcionará si tienes DIRECCIÓN a = NORTE; y luego escriba ENUM_TO_STR (a)
mathreadler
5

¿Intentaste 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 stringify()macro se puede usar para convertir cualquier texto de su código en una cadena, pero solo el texto exacto entre paréntesis. No hay desreferenciación de variables ni sustituciones de macros ni ningún otro tipo de cosa realizada.

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

M.Ali
fuente
Este será en realidad el mejor, aunque solo el primero sería suficiente :)
pholat
Funciona bien, pero debe agregar #ifndef stringify en la parte superior para evitar errores de compilación. También cambié el tipo de enumeración como std :: string como lo sugirió dgmz.
astarakastara
5

Aquí hay muchas buenas respuestas, pero pensé que algunas personas podrían encontrar la mía útil. Me gusta porque la interfaz que usas para definir la macro es lo más simple posible. 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. Extraje piezas de varios lugares en línea, así que no puedo dar crédito por todo, pero creo que es lo suficientemente único como para garantizar una nueva respuesta.

Primero haga un archivo de encabezado ... llámelo EnumMacros.h o algo así, y ponga esto en él:

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

Luego, en su programa principal, puede 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 la salida sería >> El valor de 'Apple' es: 2 de 4

¡Disfrutar!

Ph0t0n
fuente
Lo clave que me gusta de este enfoque en particular es que funciona con la sintaxis normal separada por comas de la enumeración (siempre que no incluya ninguna asignación de valores dentro de la enumeración). En mi caso, tuve que trabajar con una enumeración existente con un gran número de miembros, por lo que esto fue mucho más fácil de implementar que el enfoque de impulso.
CuriousKea
4

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 se cambia su enumeración, debe agregar / eliminar pares de pares de matriz []. Creo que es la forma más elegante de obtener una cadena de enumeración en C ++.

Vladimir
fuente
2
Además del comentario justo de que no hay necesidad de Qt aquí, otro punto es que uno podría querer usar Boost's bimapen caso de que quiera analizar nombres y convertirlos en enumeraciones (por ejemplo, desde un archivo XML).
Dmitri Nesteruk
44
En caso de no estar utilizando tipos de Qt que se trate de un genérico C ++.
Vector
3

Para C99 hay P99_DECLARE_ENUMen P99 que te permite simplemente declarar enumasí:

P99_DECLARE_ENUM(color, red, green, blue);

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

Jens Gustedt
fuente
2

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)
y2k1234
fuente
2

Un poco tarde para la fiesta, pero aquí está 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
    .................
}
Uno de uno
fuente
1
error: ‘hash’ is not a class template->#include <functional>
Ruggero Turra
2

Mi preferencia es minimizar tanto la escritura repetitiva como las macros difíciles de comprender y evitar introducir 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 como hayamos terminado con ella.

gerardw
fuente
2

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;
  }
Marco Amagliani
fuente
2

Otra tarde a 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

(Solo pongo los números de línea para que sea más fácil hablar de ellos). Las líneas 1-4 son lo 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 se trata de una técnica bien conocida llamada X-macros).

La línea 7 define la macro interna para completar la declaración de enumeración 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 al 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 en sí mismo puede ser programado 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 cientos de elementos, puede agregar, eliminar o reorganizar elementos y aún así asegurarse de que el código generado no tenga errores.

Mike Dunlavey
fuente
Las "macros X" rara vez son una solución elegante a cualquier problema. En este caso, sería mucho más legible simplemente definir los elementos macro como #define TEST_1 hello #define TEST_2 worldentonces typedef enum { TEST_1, TEST_2 } test_t;y luego crear una tabla de búsqueda de cadenas que use una macro stringify: const char* table[]= { STRINGIFY(TEST_1), STRINGIFY(TEST_2), }; ya hay múltiples respuestas que sugieren soluciones similares. Mucho más legible.
Lundin
@Lundin: solo reclamo 1) esto funciona incluso con el compilador de C más primitivo, y 2) agregar o eliminar un elemento es una edición de 1 línea.
Mike Dunlavey
He publicado una respuesta propia: stackoverflow.com/a/39877228/584518 . Con suerte, salvará a algunas pobres almas de las soluciones de macros x.
Lundin
1
Usé tu solución. Creo que es lo mejor. La sintaxis C todavía está ahí para que entiendas lo que sucede y la lista solo se define una vez. Puede eliminar el elemento 0 colocando la coma después de la entrada en su DEFINE_ENUM_ELEMENT.
fue
1

Aquí está el método Old Skool (solía usarse ampliamente en gcc) usando solo el preprocesador C. Es útil si está generando estructuras de datos discretas pero necesita mantener el orden consistente 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 )
El duque
fuente
1
Esta técnica se llama x macros!
Watusimoto
0

En c ++ así:

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";
}
BЈовић
fuente
0
#include <EnumString.h>

de 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 están duplicados.

Código de muestra para convertir un valor de enumeración 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 ) );
Andrey Syrokomskiy
fuente
0

Gracias James por tu sugerencia. Fue muy útil, así que 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;
}
Éder
fuente
0

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

El primero es la macro de uso interno, que FOR_EACH utiliza:

#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, al usarlo, puede escribir así:

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 una TUPLE, para que podamos poner un valor de suma para el miembro enum. En FOR_EACH loop, verifique el tamaño del elemento TUPLE, si el tamaño es 2, expanda el código a KEY = VALUE, de lo contrario, simplemente mantenga el primer elemento de TUPLE.

Debido a que la SEQ de entrada es en realidad TUPLEs, por lo que si desea definir las funciones STRINGIZE, es posible que primero deba procesar los enumeradores de entrada, aquí está la macro para hacer el trabajo:

#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))

La macro DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQsolo mantendrá el primer elemento en cada TUPLA, y luego se convertirá a SEQ, ahora modifica el código de James, tendrá todo el poder.

Mi implementación tal vez no sea la más simple, por lo que si no encuentra ningún código limpio, mía para su referencia.

Howard Gong
fuente
0

Solución limpia y segura en puro estándar 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

Razón fundamental

Al resolver el problema central "tener constantes de enumeración con las cadenas correspondientes", un programador sensible presentará los siguientes requisitos:

  • Evite la repetición de código (principio "SECO").
  • El código debe ser escalable, mantenible y seguro incluso si se agregan o eliminan elementos dentro de la enumeración.
  • Todo el código debe ser de alta calidad: fácil de leer, fácil de mantener.

El primer requisito, y quizás también el segundo, se puede cumplir con varias soluciones macro desordenadas, como el infame truco "x macro" u otras formas de macro magia. El problema con tales soluciones es que te dejan con un lío de macros misteriosas completamente ilegible: no cumplen con el tercer requisito anterior.

Lo único que se necesita aquí es tener una tabla de búsqueda de cadenas, a la que podamos acceder utilizando la variable enum como índice. Tal tabla debe corresponder naturalmente directamente a la enumeración y viceversa. Cuando uno de ellos se actualiza, el otro también debe actualizarse, o no funcionará.


Explicación del código.

Supongamos que tenemos una enumeración como

typedef enum
{
  hello,
  world
} test_t;

Esto se puede cambiar a

#define TEST_0 hello
#define TEST_1 world

typedef enum
{
  TEST_0,
  TEST_1,
} test_t;

Con la ventaja de que estas constantes macro ahora se pueden usar en otro lugar, por ejemplo, para generar una tabla de búsqueda de cadenas. La conversión de una constante de preprocesador a una cadena se puede hacer con una macro "stringify":

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)

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

Y eso es. Al usar hello, obtenemos la constante enum con el valor 0. Al usartest_str[hello] obtenemos la cadena "hola".

Para que la enumeración y la tabla de búsqueda correspondan directamente, debemos asegurarnos de que contengan la misma cantidad de elementos. Si alguien mantiene el código y solo cambia la enumeración, y no la tabla de búsqueda, o viceversa, este método no funcionará.

La solución es tener la enumeración para decirle cuántos elementos contiene. Hay un truco C de uso común para esto, simplemente agregue un elemento al final, que solo cumple el propósito de decir cuántos elementos tiene la enumeración:

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

Ahora podemos comprobar en tiempo de compilación que el número de elementos en la enumeración es tanto como el número de elementos en la tabla de búsqueda, preferiblemente con una afirmación estática C11:

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

(También hay formas feas pero totalmente funcionales de crear afirmaciones estáticas en versiones anteriores del estándar C, si alguien insiste en usar compiladores de dinosaurios. En cuanto a C ++, también admite afirmaciones estáticas).


Como nota al margen, en C11 también podemos lograr una mayor seguridad de tipo cambiando la macro stringify:

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

( intporque las constantes de enumeración son en realidad de tipo int, no test_t)

Esto evitará que se compile código como STRINGIFY(random_stuff).

Lundin
fuente
Entiendo lo que estás diciendo, pero el punto sigue siendo. Las alteraciones previsibles típicas deben requerir ediciones mínimas (como 1 línea). (Creo que esa es la razón detrás de DRY.) Entonces, si el tamaño de la enumeración es como 500, y desea insertar un nuevo elemento en el medio (o eliminar / renombrar / intercambiar), ¿cuántas líneas de código debe cambiar, y ¿cuánto debe hacer para asegurarse de que no cometió un error? También puede haber otras piezas de código que hagan algo uniforme para cada elemento de la lista.
Mike Dunlavey
Gracias por estas diciendo se llaman X-macros . No lo sabia. Lo que no veo es gente denigrándolos en general.
Mike Dunlavey
@MikeDunlavey No importa el tamaño de la enumeración, tendrá que cambiar exactamente 3 líneas: agregue una #define, agregue una referencia a la que se define en la declaración de enumeración y la tabla de búsqueda. Si te molestas al agregar esas líneas, el programa no se compilará. Los números que agregué a los identificadores de ninguna manera son obligatorios, también podría escribir #define APPLES helloy #define ORANGES worldseguir, typedef enum { APPES, ORANGES, TEST_N } test_t;y así sucesivamente.
Lundin el
@MikeDunlavey Con respecto a las macros X, los argumentos en contra de ellos son los mismos que los argumentos en contra de cualquier macro de función. No necesitará ir muy lejos para encontrar muchas críticas muy válidas contra macros con funciones similares.
Lundin
0

Lo que hice es una combinación de lo que he visto aquí y en preguntas similares en este sitio. Hice esto es Visual Studio 2013. No lo he probado con otros compiladores.

En primer lugar, defino un conjunto de macros que harán los trucos.

// 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

A continuación, defina una sola macro que creará la clase enum y las funciones para obtener las cadenas.

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

Ahora definir un tipo de enumeración y tener cadenas para él se vuelve realmente fácil. Todo lo que necesitas hacer es:

ENUM(MyEnumType, A, B, C);

Las siguientes líneas se pueden usar para probarlo.

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 generará:

3
A
B
C
A
B
C
A
B
C

Creo que es muy limpio y fácil de usar. Hay algunas limitaciones:

  • No puede asignar valores a los miembros de la enumeración.
  • Los valores del miembro enum se usan como índice, pero eso debería estar bien, porque todo está definido en una sola macro.
  • No puede usarlo para definir un tipo de enumeración dentro de una clase.

Si puedes evitar esto. Creo que, especialmente cómo usarlo, esto es agradable y delgado. Ventajas:

  • Fácil de usar.
  • No se requiere división de cadenas en tiempo de ejecución.
  • Hay cadenas separadas disponibles en tiempo de compilación.
  • Fácil de leer. El primer conjunto de macros puede necesitar un segundo extra, pero en realidad no es tan complicado.
jokr
fuente
0

Una solución limpia a este problema sería:

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

Lo bueno de esta solución es que es simple y también se puede construir fácilmente la función mediante copiar y reemplazar. Tenga en cuenta que si va a hacer muchas conversiones y su enumeración tiene demasiados valores posibles, esta solución podría ser intensiva en CPU.

Ali Alidoust
fuente
0

Llego un poco tarde, pero esta es mi solución usando g ++ y solo bibliotecas estándar. He tratado de minimizar la contaminación del espacio de nombres y eliminar cualquier necesidad de volver a escribir nombres enum.

El archivo de encabezado "my_enum.hpp" es:

#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 generará:

MERCURY
EARTH

Solo tiene que definir todo una vez, su espacio de nombres no debe contaminarse y todo el cálculo se realiza solo una vez (el resto son solo búsquedas). Sin embargo, no obtiene la seguridad de tipo de las clases enum (todavía son enteros cortos), no puede asignar valores a las enumeraciones, tiene que definir enumeraciones en algún lugar donde pueda definir espacios de nombres (por ejemplo, globalmente).

No estoy seguro de qué tan bueno es el rendimiento en esto, o si es una buena idea (aprendí C antes que C ++, así que mi cerebro todavía funciona de esa manera). Si alguien sabe por qué es una mala idea, no dude en señalarlo.

Alias ​​Fakename
fuente
0

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

Salidas:

Error: [0]NO_ERROR, OK
Error: [1]FILE_NOT_FOUND, Not found
Error: [2]LABEL_UNINITIALISED, Uninitialized usage
eungenue
fuente
0
#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 de enumeración extendida de clase elaborada ... no agrega ningún otro valor de enumeración que no sea el proporcionado.

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

Michal Turlik
fuente
0

Necesitaba que esto funcionara en ambas direcciones Y con frecuencia incrustaba mis enumeraciones dentro de una clase que contenía, así que comencé con la solución de James McNellis muy, muy por encima de estas respuestas, pero hice esta solución. Tenga en cuenta también que prefiero enum class en lugar de solo enum, lo que complica un poco la respuesta.

#define X_DEFINE_ENUMERATION(r, datatype, elem) case datatype::elem : return BOOST_PP_STRINGIZE(elem);

// The data portion of the FOR_EACH should be (variable type)(value)
#define X_DEFINE_ENUMERATION2(r, dataseq, elem) \
    if (BOOST_PP_SEQ_ELEM(1, dataseq) == BOOST_PP_STRINGIZE(elem) ) return BOOST_PP_SEQ_ELEM(0, dataseq)::elem;

#define DEFINE_ENUMERATION_MASTER(modifier, name, toFunctionName, enumerators)    \
    enum class name {                                                         \
        Undefined,                                                            \
        BOOST_PP_SEQ_ENUM(enumerators)                                        \
    };                                                                        \
                                                                              \
    modifier const char* ToString(const name & v)                               \
    {                                                                         \
        switch (v)                                                            \
        {                                                                     \
            BOOST_PP_SEQ_FOR_EACH(                                            \
                X_DEFINE_ENUMERATION,                                         \
                name,                                                         \
                enumerators                                                   \
            )                                                                 \
            default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
        }                                                                     \
    }                                                                         \
                                                                              \
    modifier const name toFunctionName(const std::string & value)               \
    {                                                                         \
        BOOST_PP_SEQ_FOR_EACH(                                                \
            X_DEFINE_ENUMERATION2,                                            \
            (name)(value),                                                    \
            enumerators                                                       \
        )                                                                     \
        return name::Undefined;                                               \
    }

#define DEFINE_ENUMERATION(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(inline, name, toFunctionName, enumerators)

#define DEFINE_ENUMERATION_INSIDE_CLASS(name, toFunctionName, enumerators)                 \
    DEFINE_ENUMERATION_MASTER(static, name, toFunctionName, enumerators)

Para usarlo dentro de una clase, podrías hacer algo como esto:

class ComponentStatus {
public:
    /** This is a simple bad, iffy, and good status. See other places for greater details. */
    DEFINE_ENUMERATION_INSIDE_CLASS(Status, toStatus, (RED)(YELLOW)(GREEN)
}

Y escribí una prueba CppUnit, que demuestra cómo usarla:

void
ComponentStatusTest::testSimple() {
    ComponentStatus::Status value = ComponentStatus::Status::RED;

    const char * valueStr = ComponentStatus::ToString(value);

    ComponentStatus::Status convertedValue = ComponentStatus::toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

DEFINE_ENUMERATION(Status, toStatus, (RED)(YELLOW)(GREEN))

void
ComponentStatusTest::testOutside() {
    Status value = Status::RED;

    const char * valueStr = ToString(value);

    Status convertedValue = toStatus(string(valueStr));

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

Debe elegir qué macro usar, DEFINE_ENUMERATION o DEFINE_ENUMERATION_INSIDE_CLASS. Verá que usé este último cuando definí ComponentStatus :: Status pero usé el primero cuando solo definí Status. La diferencia es simple. Dentro de una clase, prefiero los métodos to / from como "static" y si no está en una clase, uso "inline". Diferencias triviales, pero necesarias.

Desafortunadamente, no creo que haya una manera limpia de evitar tener que hacer esto:

const char * valueStr = ComponentStatus::ToString(value);

aunque podría crear manualmente un método en línea después de su definición de clase que simplemente se encadena al método de clase, algo como:

inline const char * toString(const ComponentStatus::Status value) { return ComponentStatus::ToString(value); }
Joseph Larson
fuente
0

Mi propia respuesta, no usar impulso: usar mi propio enfoque sin una gran magia de definición, y esta solución tiene la limitación de no poder definir un valor de enumeración específico.

#pragma once
#include <string>

template <class Enum>
class EnumReflect
{
public:
    static const char* getEnums() { return ""; }
};

#define DECLARE_ENUM(name, ...)                                         \
    enum name { __VA_ARGS__ };                                          \
    template <>                                                         \
    class EnumReflect<##name> {                                         \
    public:                                                             \
        static const char* getEnums() { return #__VA_ARGS__; }          \
    };

/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3,

    // comment
    enumValue4
);

    Conversion logic:

    From enumeration to string:

        printf( EnumToString(enumValue3).c_str() );

    From string to enumeration:

       enumName value;

       if( !StringToEnum("enumValue4", value) )
            printf("Conversion failed...");

    WARNING: At the moment assigning enum value to specific number is not supported.
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    const char* enums = EnumReflect<T>::getEnums();
    const char *token, *next = enums - 1;
    int id = (int)t;

    do
    {
        token = next + 1;
        if (*token == ' ') token++;
        next = strchr(token, ',');
        if (!next) next = token + strlen(token);

        if (id == 0)
            return std::string(token, next);
        id--;
    } while (*next != 0);

    return std::string();
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    const char* enums = EnumReflect<T>::getEnums();
    const char *token, *next = enums - 1;
    int id = 0;

    do
    {
        token = next + 1;
        if (*token == ' ') token++;
        next = strchr(token, ',');
        if (!next) next = token + strlen(token);

        if (strncmp(token, enumName, next - token) == 0)
        {
            t = (T)id;
            return true;
        }

        id++;
    } while (*next != 0);

    return false;
}

La última versión se puede encontrar en github aquí:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/EnumReflect.h

TarmoPikaro
fuente
0

Hay muchas otras respuestas a esto, pero creo que una mejor manera es usar las características de C ++ 17 y usar constexpr para que las traducciones se realicen en tiempo de compilación. Esto es de tipo seguro y no necesitamos jugar con macros. Vea abajo:

//enum.hpp
#include <array>
#include <string_view>

namespace Enum
{

template <class ENUM_TYPE, size_t SIZE>
constexpr ENUM_TYPE findKey(const char * value, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
    index = (index == -1) ? map.size() : index;
    return
        (index == 0) ? throw "Value not in map":
        (std::string_view(map[index - 1].second) == value) ? map[index- 1].first:
        findKey(value, map, index - 1);
};

template <class ENUM_TYPE, size_t SIZE>
constexpr const char * findValue(ENUM_TYPE key, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
    index = (index == -1) ? map.size() : index;
    return
        (index == 0) ? throw "Key not in map":
        (map[index - 1].first == key) ? map[index- 1].second:
        findValue(key, map, index - 1);
};

}

//test_enum.hpp
#include "enum.hpp"

namespace TestEnum
{
    enum class Fields
    {
        Test1,
        Test2,
        Test3,
        //This has to be at the end
        NUMBER_OF_FIELDS
    };

    constexpr std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> GetMap()
    {
        std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> map =
        {
            {
                    {Fields::Test1, "Test1"},
                    {Fields::Test2, "Test2"},
                    {Fields::Test3, "Test3"},
            }
        };
        return map;
    };

    constexpr Fields StringToEnum(const char * value)
    {
        return Enum::findKey(value, GetMap());
    }

    constexpr const char * EnumToString(Fields key)
    {
        return Enum::findValue(key, GetMap());
    }

}

Esto se puede usar fácilmente para que los errores de clave de cadena se detecten en tiempo de compilación:

#include "test_enum.hpp"

int main()
{
    auto constexpr a = TestEnum::StringToEnum("Test2"); //a = TestEnum::Fields::Test2
    auto constexpr b = TestEnum::EnumToString(TestEnum::Fields::Test1); //b = "Test1"
    auto constexpr c = TestEnum::StringToEnum("AnyStringNotInTheMap"); //compile time failure
    return 0;
}

El código es más detallado que algunas otras soluciones, pero podemos hacer fácilmente la conversión de Enum a String y la conversión de String a Enum en tiempo de compilación y detectar errores de tipo. Con algunas de las características futuras de C ++ 20, esto probablemente se pueda simplificar un poco más.

Mario
fuente