¿Manera fácil de usar variables de tipos de enumeración como cadena en C?

87

Esto es lo que estoy tratando de hacer:

typedef enum { ONE, TWO, THREE } Numbers;

Estoy tratando de escribir una función que haga un cambio de caso similar al siguiente:

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

En lugar de definir en cada caso, ¿hay alguna manera de configurarlo usando la variable enum como estoy tratando de hacer arriba?

zxcv
fuente

Respuestas:

14

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

sk.
fuente
Ampliando esto, si de hecho es una lista que se incrementa linealmente, puede usar la herramienta de macro de su editor para registrar y resolver cada uno de los nombres en una cadena. Se necesita poca escritura adicional y, en primer lugar, ahorra la necesidad de definir. Hago clic en grabar en la última de las macros copiadas, agrego una cita después y procedo al mismo lugar en la siguiente línea. Empujo el stop. Presiono ejecutar X veces y hago todas las que haya (o solo un solo paso). Luego puedo envolverlo en una matriz de cadenas.
user2262111
70

¿La técnica de hacer algo a la vez un identificador C y una cadena? se puede utilizar aquí.

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

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

enumFactory.h:

// expansion macro for enum value definition
#define ENUM_VALUE(name,assign) name assign,

// expansion macro for enum to string conversion
#define ENUM_CASE(name,assign) case name: return #name;

// expansion macro for string to enum conversion
#define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name;

/// declare the access function and define enum values
#define DECLARE_ENUM(EnumType,ENUM_DEF) \
  enum EnumType { \
    ENUM_DEF(ENUM_VALUE) \
  }; \
  const char *GetString(EnumType dummy); \
  EnumType Get##EnumType##Value(const char *string); \

/// define the access function names
#define DEFINE_ENUM(EnumType,ENUM_DEF) \
  const char *GetString(EnumType value) \
  { \
    switch(value) \
    { \
      ENUM_DEF(ENUM_CASE) \
      default: return ""; /* handle input error */ \
    } \
  } \
  EnumType Get##EnumType##Value(const char *str) \
  { \
    ENUM_DEF(ENUM_STRCMP) \
    return (EnumType)0; /* handle input error */ \
  } \

Utilizado en la fábrica

someEnum.h:

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

DECLARE_ENUM(SomeEnum,SOME_ENUM)

someEnum.cpp:

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

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

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

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

Suma
fuente
2
No estoy seguro de cómo se puede decir que esto es mejor / peor que x-macros, esto es x-macros. El SOME_ENUM(XX)es exactamente un X-macro (para ser precisos, la "forma de usuario" que pasa a la XXfunción en lugar de usar #def #undef) y luego gire en el conjunto X-MACRO se pasa entonces a DEFINE_ENUM que lo utiliza. Sin quitarle nada a la solución, funciona bien. Solo para aclarar que es un uso de macros X.
BeeOnRope
1
@BeeOnRope La diferencia que observa es significativa y distingue esta solución de las macros X idiomáticas (como los ejemplos de Wikipedia ). La ventaja de pasar por XXalto reing #definees que el patrón anterior se puede utilizar en macro expansiones. Tenga en cuenta que las únicas otras soluciones tan concisas como esta requieren la creación y la inclusión múltiple de un archivo separado para definir una nueva enumeración.
pmttavara
1
Otro truco consiste en utilizar el nombre de enumeración como nombre de macro. Simplemente puede escribir #define DEFINE_ENUM(EnumType) ..., reemplazar ENUM_DEF(...)con EnumType(...)y hacer que el usuario diga #define SomeEnum(XX) .... El preprocesador de C se expandirá contextualmente SomeEnumen la invocación de macro cuando esté seguido de paréntesis y en un token regular de lo contrario. (Por supuesto, esto causa problemas si al usuario le gusta usar SomeEnum(2)para convertir al tipo de enumeración en lugar de (SomeEnum)2o static_cast<SomeEnum>(2).)
pmttavara
1
@pmttavara: claro, si una búsqueda rápida es una indicación, el uso más común de x-macros usa un nombre de macro interno fijo junto con #definey #undef. ¿No está de acuerdo con que el "formulario de usuario" (sugerido, por ejemplo, al final de este artículo ) sea un tipo de macro x? Ciertamente, siempre lo he llamado macro x y en las bases de código C en las que he estado últimamente es la forma más común (eso es obviamente una observación sesgada). Sin embargo, es posible que haya estado analizando mal el OP.
BeeOnRope
2
@BeeOnRope La redacción actual es el resultado de la edición, como me convenciste en ese entonces, esto es x-macro, incluso si quizás era una forma menos utilizada (o al menos una menos mencionada en los artículos) en ese entonces.
Suma
62
// Define your enumeration like this (in say numbers.h);
ENUM_BEGIN( Numbers )
    ENUM(ONE),
    ENUM(TWO),
    ENUM(FOUR)
ENUM_END( Numbers )

// The macros are defined in a more fundamental .h file (say defs.h);
#define ENUM_BEGIN(typ) enum typ {
#define ENUM(nam) nam
#define ENUM_END(typ) };

// Now in one and only one .c file, redefine the ENUM macros and reinclude
//  the numbers.h file to build a string table
#undef ENUM_BEGIN
#undef ENUM
#undef ENUM_END
#define ENUM_BEGIN(typ) const char * typ ## _name_table [] = {
#define ENUM(nam) #nam
#define ENUM_END(typ) };
#undef NUMBERS_H_INCLUDED   // whatever you need to do to enable reinclusion
#include "numbers.h"

// Now you can do exactly what you want to do, with no retyping, and for any
//  number of enumerated types defined with the ENUM macro family
//  Your code follows;
char num_str[10];
int process_numbers_str(Numbers num) {
  switch(num) {
    case ONE:
    case TWO:
    case THREE:
    {
      strcpy(num_str, Numbers_name_table[num]); // eg TWO -> "TWO"
    } break;
    default:
      return 0; //no match
  return 1;
}

// Sweet no ? After being frustrated by this for years, I finally came up
//  with this solution for my most recent project and plan to reuse the idea
//  forever
Bill Forster
fuente
3
Este es el tipo de cosas para las que se creó cpp. +1.
Derrick Turk
6
Esta es una buena respuesta, parece ser lo mejor que se puede hacer sin usar herramientas especiales, y he hecho este tipo de cosas antes; pero todavía nunca se siente realmente 'bien' y nunca me gusta hacerlo ...
Michael Burr
Pequeño cambio: #define ENUM_END(typ) }; extern const char * typ ## _name_table[];en el defs.harchivo: esto declarará su tabla de nombres en los archivos que la use. (Sin embargo, no puedo encontrar una buena manera de declarar el tamaño de la tabla). Además, personalmente dejaría el punto y coma final, pero los méritos son discutibles de cualquier manera.
Chris Lutz
1
@Bill, ¿Por qué molestarse con typen la fila #define ENUM_END(typ) };?
Pacerier
Esto no funciona donde quiero que mi macro se defina como "ONE = 5"
UKMonkey
13

Definitivamente hay una manera de hacer esto: use macros X () . Estas macros utilizan el preprocesador de C para construir enumeraciones, matrices y bloques de código a partir de una lista de datos de origen. Solo necesita agregar nuevos elementos a la #define que contiene la macro X (). La declaración de cambio se expandiría automáticamente.

Su ejemplo se puede escribir de la siguiente manera:

 // Source data -- Enum, String
 #define X_NUMBERS \
    X(ONE,   "one") \
    X(TWO,   "two") \
    X(THREE, "three")

 ...

 // Use preprocessor to create the Enum
 typedef enum {
  #define X(Enum, String)       Enum,
   X_NUMBERS
  #undef X
 } Numbers;

 ...

 // Use Preprocessor to expand data into switch statement cases
 switch(num)
 {
 #define X(Enum, String) \
     case Enum:  strcpy(num_str, String); break;
 X_NUMBERS
 #undef X

     default: return 0; break;
 }
 return 1;

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

JayG
fuente
8

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

Te permite hacer esto:

#define MACROSTR(k) #k

typedef enum {
    kZero,
    kOne,
    kTwo,
    kThree
} kConst;

static char *kConstStr[] = {
    MACROSTR(kZero),
    MACROSTR(kOne),
    MACROSTR(kTwo),
    MACROSTR(kThree)
};

static void kConstPrinter(kConst k)
{
    printf("%s", kConstStr[k]);
}
pedestal
fuente
char const *kConstStr[]
Anne van Rossum
6

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

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

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

Por no disperso, me refiero a no de la forma

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

ya que tiene enormes lagunas.

La ventaja de este método es que coloca las definiciones de las enumeraciones y cadenas cerca una de la otra; tener una instrucción de cambio en una función los especifica. Esto significa que es menos probable que cambie uno sin el otro.

paxdiablo
fuente
6

BESO. Hará todo tipo de cosas de cambio / caso con sus enumeraciones, entonces, ¿por qué la impresión debería ser diferente? Olvidar un caso en su rutina de impresión no es un gran problema cuando considera que hay alrededor de 100 lugares en los que puede olvidar un caso. Simplemente compile -Wall, que le advertirá de coincidencias de casos no exhaustivas. No use "predeterminado" porque eso hará que el cambio sea exhaustivo y no recibirá advertencias. En su lugar, deje que el interruptor salga y maneje el caso predeterminado así ...

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

El uso de boost :: preprocessor hace posible una solución elegante como la siguiente:

Paso 1: incluye el archivo de encabezado:

#include "EnumUtilities.h"

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

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

Paso 3: usa tus datos:

Obteniendo la cantidad de elementos:

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

Obteniendo la cadena asociada:

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

Obteniendo el valor de enumeración de la cadena asociada:

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

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

#include <boost/preprocessor/seq/for_each.hpp>
#include <string>

#define REALLY_MAKE_STRING(x) #x
#define MAKE_STRING(x) REALLY_MAKE_STRING(x)
#define MACRO1(r, data, elem) elem,
#define MACRO1_STRING(r, data, elem)    case elem: return REALLY_MAKE_STRING(elem);
#define MACRO1_ENUM(r, data, elem)      if (REALLY_MAKE_STRING(elem) == eStrEl) return elem;


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

Hay alguna limitación, es decir, las de boost :: preprocessor. En este caso, la lista de constantes no puede tener más de 64 elementos.

Siguiendo la misma lógica, también podría pensar en crear una enumeración dispersa:

#define EnumName(Tuple)                 BOOST_PP_TUPLE_ELEM(2, 0, Tuple)
#define EnumValue(Tuple)                BOOST_PP_TUPLE_ELEM(2, 1, Tuple)
#define MACRO2(r, data, elem)           EnumName(elem) EnumValue(elem),
#define MACRO2_STRING(r, data, elem)    case EnumName(elem): return BOOST_PP_STRINGIZE(EnumName(elem));

#define MakeEnumEx(eName, SEQ) \
    enum eName { \
    BOOST_PP_SEQ_FOR_EACH(MACRO2, _, SEQ) \
    last_##eName##_enum }; \
    const int eName##Count = BOOST_PP_SEQ_SIZE(SEQ); \
    static std::string eName##2String(const enum eName eel) \
    { \
        switch (eel) \
        { \
        BOOST_PP_SEQ_FOR_EACH(MACRO2_STRING, _, SEQ) \
        default: return "Unknown enumerator value."; \
        }; \
    };  

En este caso, la sintaxis es:

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

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

Lo probé en mac y linux, pero tenga en cuenta que es posible que boost :: preprocessor no sea completamente portátil.

Giacomo M.
fuente
3

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

#define MACROSTR(k) #k

#define X_NUMBERS \
       X(kZero  ) \
       X(kOne   ) \
       X(kTwo   ) \
       X(kThree ) \
       X(kFour  ) \
       X(kMax   )

enum {
#define X(Enum)       Enum,
    X_NUMBERS
#undef X
} kConst;

static char *kConstStr[] = {
#define X(String) MACROSTR(String),
    X_NUMBERS
#undef X
};

int main(void)
{
    int k;
    printf("Hello World!\n\n");

    for (k = 0; k < kMax; k++)
    {
        printf("%s\n", kConstStr[k]);
    }

    return 0;
}
Juan González Burgos
fuente
2

Si está usando gcc, es posible usar:

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

Entonces solo llama por ejemplo

enum_to_string_map[enum1]
janisj
fuente
1

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

Encabezado: paste1.h

/*
@(#)File:           $RCSfile: paste1.h,v $
@(#)Version:        $Revision: 1.1 $
@(#)Last changed:   $Date: 2008/05/17 21:38:05 $
@(#)Purpose:        Automated Token Pasting
*/

#ifndef JLSS_ID_PASTE_H
#define JLSS_ID_PASTE_H

/*
 * Common case when someone just includes this file.  In this case,
 * they just get the various E* tokens as good old enums.
 */
#if !defined(ETYPE)
#define ETYPE(val, desc) E##val,
#define ETYPE_ENUM
enum {
#endif /* ETYPE */

   ETYPE(PERM,  "Operation not permitted")
   ETYPE(NOENT, "No such file or directory")
   ETYPE(SRCH,  "No such process")
   ETYPE(INTR,  "Interrupted system call")
   ETYPE(IO,    "I/O error")
   ETYPE(NXIO,  "No such device or address")
   ETYPE(2BIG,  "Arg list too long")

/*
 * Close up the enum block in the common case of someone including
 * this file.
 */
#if defined(ETYPE_ENUM)
#undef ETYPE_ENUM
#undef ETYPE
ETYPE_MAX
};
#endif /* ETYPE_ENUM */

#endif /* JLSS_ID_PASTE_H */

Fuente de ejemplo:

/*
@(#)File:           $RCSfile: paste1.c,v $
@(#)Version:        $Revision: 1.2 $
@(#)Last changed:   $Date: 2008/06/24 01:03:38 $
@(#)Purpose:        Automated Token Pasting
*/

#include "paste1.h"

static const char *sys_errlist_internal[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) desc,
#include "paste1.h"
    0
#undef ETYPE
};

static const char *xerror(int err)
{
    if (err >= ETYPE_MAX || err <= 0)
        return "Unknown error";
    return sys_errlist_internal[err];
}

static const char*errlist_mnemonics[] = {
#undef JLSS_ID_PASTE_H
#define ETYPE(val, desc) [E ## val] = "E" #val,
#include "paste1.h"
#undef ETYPE
};

#include <stdio.h>

int main(void)
{
    int i;

    for (i = 0; i < ETYPE_MAX; i++)
    {
        printf("%d: %-6s: %s\n", i, errlist_mnemonics[i], xerror(i));
    }
    return(0);
}

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

Jonathan Leffler
fuente
0

Si el índice de enumeración está basado en 0, puede poner los nombres en una matriz de char * e indexarlos con el valor de enumeración.

Colen
fuente
0

He creado una clase simple de plantilla streamable_enumque utiliza flujo de los operadores <<y >>, y se basa en la std::map<Enum, std::string>:

#ifndef STREAMABLE_ENUM_HPP
#define STREAMABLE_ENUM_HPP

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

template <typename E>
class streamable_enum
{
public:
    typedef typename std::map<E, std::string> tostr_map_t;
    typedef typename std::map<std::string, E> fromstr_map_t;

    streamable_enum()
    {}

    streamable_enum(E val) :
        Val_(val)
    {}

    operator E() {
        return Val_;
    }

    bool operator==(const streamable_enum<E>& e) {
        return this->Val_ == e.Val_;
    }

    bool operator==(const E& e) {
        return this->Val_ == e;
    }

    static const tostr_map_t& to_string_map() {
        static tostr_map_t to_str_(get_enum_strings<E>());
        return to_str_;
    }

    static const fromstr_map_t& from_string_map() {
        static fromstr_map_t from_str_(reverse_map(to_string_map()));
        return from_str_;
    }
private:
    E Val_;

    static fromstr_map_t reverse_map(const tostr_map_t& eToS) {
        fromstr_map_t sToE;
        for (auto pr : eToS) {
            sToE.emplace(pr.second, pr.first);
        }
        return sToE;
    }
};

template <typename E>
streamable_enum<E> stream_enum(E e) {
    return streamable_enum<E>(e);
}

template <typename E>
typename streamable_enum<E>::tostr_map_t get_enum_strings() {
    // \todo throw an appropriate exception or display compile error/warning
    return {};
}

template <typename E>
std::ostream& operator<<(std::ostream& os, streamable_enum<E> e) {
    auto& mp = streamable_enum<E>::to_string_map();
    auto res = mp.find(e);
    if (res != mp.end()) {
        os << res->second;
    } else {
        os.setstate(std::ios_base::failbit);
    }
    return os;
}

template <typename E>
std::istream& operator>>(std::istream& is, streamable_enum<E>& e) {
    std::string str;
    is >> str;
    if (str.empty()) {
        is.setstate(std::ios_base::failbit);
    }
    auto& mp = streamable_enum<E>::from_string_map();
    auto res = mp.find(str);
    if (res != mp.end()) {
        e = res->second;
    } else {
        is.setstate(std::ios_base::failbit);
    }
    return is;
}

#endif

Uso:

#include "streamable_enum.hpp"

using std::cout;
using std::cin;
using std::endl;

enum Animal {
    CAT,
    DOG,
    TIGER,
    RABBIT
};

template <>
streamable_enum<Animal>::tostr_map_t get_enum_strings<Animal>() {
    return {
        { CAT, "Cat"},
        { DOG, "Dog" },
        { TIGER, "Tiger" },
        { RABBIT, "Rabbit" }
    };
}

int main(int argc, char* argv []) {
    cout << "What animal do you want to buy? Our offering:" << endl;
    for (auto pr : streamable_enum<Animal>::to_string_map()) {          // Use from_string_map() and pr.first instead
        cout << " " << pr.second << endl;                               // to have them sorted in alphabetical order
    }
    streamable_enum<Animal> anim;
    cin >> anim;
    if (!cin) {
        cout << "We don't have such animal here." << endl;
    } else if (anim == Animal::TIGER) {
        cout << stream_enum(Animal::TIGER) << " was a joke..." << endl;
    } else {
        cout << "Here you are!" << endl;
    }

    return 0;
}
Robert Husák
fuente
0

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

  1. solo escriba cada valor de la enumeración una vez, por lo que no hay listas dobles para mantener

  2. no guarde los valores de enumeración en un archivo separado que luego se #incluye, para que pueda escribirlo donde quiera

  3. no reemplace la enumeración en sí, todavía quiero tener definido el tipo de enumeración, pero además quiero poder asignar cada nombre de enumeración a la cadena correspondiente (para no afectar el código heredado)

  4. la búsqueda debe ser rápida, así que preferiblemente sin cambiar mayúsculas y minúsculas, para esas enormes enumeraciones

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

muqker
fuente
0

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

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

#ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP
#define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP

#include <swissarmyknife/detail/config.hpp>

#include <string>
#include <ostream>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>


#define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C(                     \
    R, unused, ENUMERATION_ENTRY)                                               \
    case ENUMERATION_ENTRY:                                                     \
      return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY);                             \
    break;                                                                      

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

#endif

La respuesta anterior a continuación es bastante mala, no la uses. :)

Respuesta anterior:

He estado buscando una forma que resuelva este problema sin cambiar demasiado la sintaxis de declaración de enumeraciones. Llegué a una solución que usa el preprocesador para recuperar una cadena de una declaración de enumeración en cadena.

Puedo definir enumeraciones no dispersas como esta:

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

Y puedo interactuar con ellos de diferentes formas:

// With a stringstream
std::stringstream ss;
ss << State::FAULT;
std::string myEnumStr = ss.str();

//Directly to stdout
std::cout << State::FAULT << std::endl;

//to a string
std::string myStr = State::to_string(State::FAULT);

//from a string
State::State myEnumVal = State::from_string(State::FAULT);

Basado en las siguientes definiciones:

#define SMART_ENUM(enumTypeArg, ...)                                                     \
namespace enumTypeArg {                                                                  \
    __VA_ARGS__;                                                                         \
    std::ostream& operator<<(std::ostream& os, const enumTypeArg& val) {                 \
            os << swissarmyknife::enums::to_string(#__VA_ARGS__, val);                   \
            return os;                                                                   \
    }                                                                                    \
                                                                                     \
    std::string to_string(const enumTypeArg& val) {                                      \
            return swissarmyknife::enums::to_string(#__VA_ARGS__, val);                  \
    }                                                                                    \
                                                                                     \
    enumTypeArg from_string(const std::string &str) {                                    \
            return swissarmyknife::enums::from_string<enumTypeArg>(#__VA_ARGS__, str);   \
    }                                                                                    \
}                                                                                        \


namespace swissarmyknife { namespace enums {

    static inline std::string to_string(const std::string completeEnumDeclaration, size_t enumVal) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            if (enumVal == count) {
                std::string identifiersSubset = identifiers.substr(0, found);
                size_t beginId = identifiersSubset.find_last_of("{,");
                identifiersSubset = identifiersSubset.substr(beginId+1);
                boost::algorithm::trim(identifiersSubset);
                return identifiersSubset;
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("The enum declaration provided doesn't contains this state.");
    }                                                  

    template <typename EnumType>
    static inline EnumType from_string(const std::string completeEnumDeclaration, const std::string &enumStr) throw (std::runtime_error) {
        size_t begin = completeEnumDeclaration.find_first_of('{');
        size_t end = completeEnumDeclaration.find_last_of('}');
        const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );

        size_t count = 0;
        size_t found = 0;
        do {
            found = identifiers.find_first_of(",}", found+1);

            std::string identifiersSubset = identifiers.substr(0, found);
            size_t beginId = identifiersSubset.find_last_of("{,");
            identifiersSubset = identifiersSubset.substr(beginId+1);
            boost::algorithm::trim(identifiersSubset);

            if (identifiersSubset == enumStr) {
                return static_cast<EnumType>(count);
            }

            ++count;
        } while (found != std::string::npos);

        throw std::runtime_error("No valid enum value for the provided string");
    }                      

}}

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

De lo contrario, siempre usé boost :: bimap para realizar tales asignaciones entre el valor de enumeraciones y la cadena, pero debe mantenerse manualmente.

daminetreg
fuente
0

Como prefiero no usar macros por todas las razones habituales, utilicé una solución de macros más limitada que tiene la ventaja de mantener libre la macro de declaración de enumeración. Las desventajas incluyen tener que copiar y pegar la definición de macro para cada enumeración y tener que agregar explícitamente una invocación de macro al agregar valores a la enumeración.

std::ostream& operator<<(std::ostream& os, provenance_wrapper::CaptureState cs)
{
#define HANDLE(x) case x: os << #x; break;
    switch (cs) {
    HANDLE(CaptureState::UNUSED)
    HANDLE(CaptureState::ACTIVE)
    HANDLE(CaptureState::CLOSED)
    }
    return os;
#undef HANDLE
}
gerardw
fuente