enumeración para encadenar en C ++ 11 / C ++ 14 / C ++ 17 moderno y C ++ 20 futuro


354

Contrariamente a todas las otras preguntas similares, esta pregunta trata sobre el uso de las nuevas características de C ++.

Después de leer muchas respuestas, todavía no encontré ninguna:

Ejemplo

Un ejemplo suele ser mejor que una larga explicación.
Puede compilar y ejecutar este fragmento en Coliru .
( Otro ejemplo anterior también está disponible)

#include <map>
#include <iostream>

struct MyClass
{
    enum class MyEnum : char {
        AAA = -8,
        BBB = '8',
        CCC = AAA + BBB
    };
};

// Replace magic() by some faster compile-time generated code
// (you're allowed to replace the return type with std::string
// if that's easier for you)
const char* magic (MyClass::MyEnum e)
{
    const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
        { MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
        { MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
        { MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
    };
    auto   it  = MyEnumStrings.find(e);
    return it == MyEnumStrings.end() ? "Out of range" : it->second;
}

int main()
{
   std::cout << magic(MyClass::MyEnum::AAA) <<'\n';
   std::cout << magic(MyClass::MyEnum::BBB) <<'\n';
   std::cout << magic(MyClass::MyEnum::CCC) <<'\n';
}

Restricciones

  • Por favor, no hay duplicación sin valor de otras respuestas o enlaces básicos .
  • Evite la respuesta inflada basada en macros o intente reducir la #definesobrecarga lo más mínimo posible.
  • Por favor no manual enum-> stringmapeo.

Agradable tener

  • enumValores de soporte que comienzan desde un número diferente de cero
  • Apoyar enumvalores negativos
  • Apoyar enumvalores fragmentados
  • Soporte class enum(C ++ 11)
  • Soporte class enum : <type>teniendo cualquier permitido <type>(C ++ 11)
  • Conversiones en tiempo de compilación (no en tiempo de ejecución) a una cadena,
    o al menos ejecución rápida en tiempo de ejecución (por ejemplo, std::mapno es una gran idea ...)
  • constexpr (C ++ 11, luego relajado en C ++ 14/17/20)
  • noexcept (C ++ 11)
  • Fragmento amigable C ++ 17 / C ++ 20

Una idea podría ser posible utilizando las capacidades de C ++ Compiler para generar código C ++ durante la compilación en tiempo usando trucos meta de programación basados en variadic template classy constexprfunciones ...


44
(quizás de tema) mira este blog relacionado con Qt. woboq.com/blog/reflection-in-cpp-and-qt-moc.html . Describe la posibilidad de reemplazar el moc de Qt (compilador de metaobjetos) mediante el uso de la reflexión de C ++ (estándar propuesto).
ibre5041

10
N4113 :std::enumerator::identifier_v<MyEnum, MyEnum::AAA>
ecatmur

1
Personalmente, resolví este problema mediante la implementación de una pequeña biblioteca de utilidad de preprocesador que me permite recorrer argumentos macro variables y realizar una función sobre todos ellos. Paso los valores de enumeración como argumentos macro y genero automáticamente la enumeración y la matriz de cadenas a través del preprocesador. Probablemente también puedas hacer esto usando el preprocesador Boost.
Vittorio Romeo

2
¿Todo tiene que ser resuelto con C ++? Es muy fácil generar automáticamente código para la representación de cadena, solo un par de líneas de código.
Karoly Horvath

2
"Por favor, no proporcionar respuestas basadas en C macro si es posible", así, a menos que esté dispuesto a esperar para C ++ 17 no es muy usadas nada, y no es que mal para declarar sus enumeraciones tan DEC_ENUM(enumname, (a,b,c,(d,b),(e,42)))a menos que tenga a la maintaint generar macro ... y, en mi opinión, poner tales casos en el lenguaje es solo otro tipo de pirateo en lugar de una plantilla / macro híbrido más potente. No deberíamos agregar todos esos casos de uso útiles de macros al lenguaje solo para poder decir que las macros ya no tienen uso.
PlasmaHH

Respuestas:


43

La biblioteca de solo encabezado Magic Enum proporciona reflexión estática para las enumeraciones (a la cadena, de la cadena, la iteración) para C ++ 17.

#include <magic_enum.hpp>

enum Color { RED = 2, BLUE = 4, GREEN = 8 };

Color color = Color::RED;
auto color_name = magic_enum::enum_name(color);
// color_name -> "RED"

std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name)
if (color.has_value()) {
  // color.value() -> Color::GREEN
};

Para obtener más ejemplos, consulte el repositorio de inicio https://github.com/Neargye/magic_enum .

¿Dónde está el inconveniente?

Esta biblioteca utiliza un truco específico del compilador (basado en __PRETTY_FUNCTION__/ __FUNCSIG__), que funciona en Clang> = 5, MSVC> = 15.3 y GCC> = 9.

El valor de enumeración debe estar dentro del rango [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX].

  • Por defecto MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128.

  • Si necesita otro rango para todos los tipos de enumeración de forma predeterminada, redefina la macro MAGIC_ENUM_RANGE_MINy MAGIC_ENUM_RANGE_MAX.

  • MAGIC_ENUM_RANGE_MINdebe ser menor o igual que 0y debe ser mayor que INT16_MIN.

  • MAGIC_ENUM_RANGE_MAXdebe ser mayor que 0y debe ser menor que INT16_MAX.

  • Si necesita otro rango para un tipo de enumeración específico, agregue la especialización enum_range para el tipo de enumeración necesario.

    #include <magic_enum.hpp>
    
    enum number { one = 100, two = 200, three = 300 };
    
    namespace magic_enum {
    template <>
      struct enum_range<number> {
        static constexpr int min = 100;
        static constexpr int max = 300;
    };
    }

¿Por qué el rango limita? ¿Es para limitar algún tipo de profundidad de recursión, o debido a algún tipo de búsqueda lineal en tiempo de compilación?
Emile Cormier

Esto es increíble. ¡Gracias! Probablemente sea incluso eficiente si el compilador es lo suficientemente inteligente como para evaluar constexpr std :: array solo una vez. Muy, muy agradable.
iestyn

87

(El enfoque de los mejores_enums biblioteca )

Hay una manera de hacer enum a string en C ++ actual que se ve así:

ENUM(Channel, char, Red = 1, Green, Blue)

// "Same as":
// enum class Channel : char { Red = 1, Green, Blue };

Uso:

Channel     c = Channel::_from_string("Green");  // Channel::Green (2)
c._to_string();                                  // string "Green"

for (Channel c : Channel::_values())
    std::cout << c << std::endl;

// And so on...

Todas las operaciones pueden hacerse constexpr. También puede implementar la propuesta de reflexión C ++ 17 mencionada en la respuesta de @ecatmur.

  • Solo hay una macro. Creo que este es el mínimo posible, porque la secuenciación del preprocesador (# ) es la única forma de convertir un token en una cadena en C ++ actual.
  • La macro es bastante discreta: las declaraciones constantes, incluidos los inicializadores, se pegan en una declaración de enumeración incorporada. Esto significa que tienen la misma sintaxis y significado que en una enumeración incorporada.
  • La repetición es eliminada.
  • La implementación es más natural y útil en al menos C ++ 11, debido a constexpr. También se puede hacer que funcione con C ++ 98 + __VA_ARGS__. Definitivamente es C ++ moderno.

La definición de la macro está algo involucrada, así que estoy respondiendo esto de varias maneras.

  • La mayor parte de esta respuesta es una implementación que creo que es adecuada para las limitaciones de espacio en StackOverflow.
  • También hay un artículo de CodeProject que describe los conceptos básicos de la implementación en un tutorial de formato largo. [ ¿Debería moverlo aquí? Creo que es demasiado para una respuesta SO ].
  • Hay una biblioteca con todas las funciones "Mejores enumeraciones" que implementa la macro en un solo archivo de encabezado. También implementa consultas de propiedades de tipo N4428 , la revisión actual de la propuesta de reflexión C ++ 17 N4113. Entonces, al menos para las enumeraciones declaradas a través de esta macro, puede tener la reflexión de enumeración C ++ 17 propuesta ahora, en C ++ 11 / C ++ 14.

Es sencillo extender esta respuesta a las características de la biblioteca: aquí no queda nada "importante". Sin embargo, es bastante tedioso y existen problemas de portabilidad del compilador.

Descargo de responsabilidad : soy el autor tanto del artículo de CodeProject como de la biblioteca.

Puede probar el código en esta respuesta , la biblioteca y la implementación de N4428 en vivo en línea en Wandbox. La documentación de la biblioteca también contiene una descripción general de cómo usarla como N4428 , que explica la parte de enumeraciones de esa propuesta.


Explicación

El siguiente código implementa conversiones entre enumeraciones y cadenas. Sin embargo, se puede extender para hacer otras cosas también, como la iteración. Esta respuesta envuelve una enumeración en a struct. También puedes generar rasgosstruct junto con una enumeración.

La estrategia es generar algo como esto:

struct Channel {
    enum _enum : char { __VA_ARGS__ };
    constexpr static const Channel          _values[] = { __VA_ARGS__ };
    constexpr static const char * const     _names[] = { #__VA_ARGS__ };

    static const char* _to_string(Channel v) { /* easy */ }
    constexpr static Channel _from_string(const char *s) { /* easy */ }
};

Los problemas son:

  1. Terminaremos con algo así {Red = 1, Green, Blue}como el inicializador de la matriz de valores. Esto no es válido en C ++, porque Redno es una expresión asignable. Esto se soluciona mediante colada cada constante a un tipo Tque tiene un operador de asignación, pero se reducirá la misión: {(T)Red = 1, (T)Green, (T)Blue}.
  2. Del mismo modo, terminaremos con {"Red = 1", "Green", "Blue"}el inicializador de la matriz de nombres. Tendremos que recortar el " = 1". No conozco una excelente manera de hacer esto en tiempo de compilación, por lo que aplazaremos esto al tiempo de ejecución. Como resultado, _to_stringno lo será constexpr, pero _from_stringaún puede serlo constexpr, porque podemos tratar los espacios en blanco y los signos iguales como terminadores cuando se comparan con cadenas sin recortar.
  3. Tanto lo anterior necesita una macro de "mapeo" que puede aplicar otra macro a cada elemento __VA_ARGS__. Esto es bastante estándar. Esta respuesta incluye una versión simple que puede manejar hasta 8 elementos.
  4. Si la macro debe ser verdaderamente autónoma, debe declarar que no hay datos estáticos que requieran una definición separada. En la práctica, esto significa que las matrices necesitan un tratamiento especial. Hay dos soluciones posibles: constexpr(o simplemente const) matrices en el ámbito del espacio de nombres, o matrices regulares en constexprfunciones en línea no estáticas. El código en esta respuesta es para C ++ 11 y toma el enfoque anterior. El artículo de CodeProject es para C ++ 98 y toma el último.

Código

#include <cstddef>      // For size_t.
#include <cstring>      // For strcspn, strncpy.
#include <stdexcept>    // For runtime_error.



// A "typical" mapping macro. MAP(macro, a, b, c, ...) expands to
// macro(a) macro(b) macro(c) ...
// The helper macro COUNT(a, b, c, ...) expands to the number of
// arguments, and IDENTITY(x) is needed to control the order of
// expansion of __VA_ARGS__ on Visual C++ compilers.
#define MAP(macro, ...) \
    IDENTITY( \
        APPLY(CHOOSE_MAP_START, COUNT(__VA_ARGS__)) \
            (macro, __VA_ARGS__))

#define CHOOSE_MAP_START(count) MAP ## count

#define APPLY(macro, ...) IDENTITY(macro(__VA_ARGS__))

#define IDENTITY(x) x

#define MAP1(m, x)      m(x)
#define MAP2(m, x, ...) m(x) IDENTITY(MAP1(m, __VA_ARGS__))
#define MAP3(m, x, ...) m(x) IDENTITY(MAP2(m, __VA_ARGS__))
#define MAP4(m, x, ...) m(x) IDENTITY(MAP3(m, __VA_ARGS__))
#define MAP5(m, x, ...) m(x) IDENTITY(MAP4(m, __VA_ARGS__))
#define MAP6(m, x, ...) m(x) IDENTITY(MAP5(m, __VA_ARGS__))
#define MAP7(m, x, ...) m(x) IDENTITY(MAP6(m, __VA_ARGS__))
#define MAP8(m, x, ...) m(x) IDENTITY(MAP7(m, __VA_ARGS__))

#define EVALUATE_COUNT(_1, _2, _3, _4, _5, _6, _7, _8, count, ...) \
    count

#define COUNT(...) \
    IDENTITY(EVALUATE_COUNT(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1))



// The type "T" mentioned above that drops assignment operations.
template <typename U>
struct ignore_assign {
    constexpr explicit ignore_assign(U value) : _value(value) { }
    constexpr operator U() const { return _value; }

    constexpr const ignore_assign& operator =(int dummy) const
        { return *this; }

    U   _value;
};



// Prepends "(ignore_assign<_underlying>)" to each argument.
#define IGNORE_ASSIGN_SINGLE(e) (ignore_assign<_underlying>)e,
#define IGNORE_ASSIGN(...) \
    IDENTITY(MAP(IGNORE_ASSIGN_SINGLE, __VA_ARGS__))

// Stringizes each argument.
#define STRINGIZE_SINGLE(e) #e,
#define STRINGIZE(...) IDENTITY(MAP(STRINGIZE_SINGLE, __VA_ARGS__))



// Some helpers needed for _from_string.
constexpr const char    terminators[] = " =\t\r\n";

// The size of terminators includes the implicit '\0'.
constexpr bool is_terminator(char c, size_t index = 0)
{
    return
        index >= sizeof(terminators) ? false :
        c == terminators[index] ? true :
        is_terminator(c, index + 1);
}

constexpr bool matches_untrimmed(const char *untrimmed, const char *s,
                                 size_t index = 0)
{
    return
        is_terminator(untrimmed[index]) ? s[index] == '\0' :
        s[index] != untrimmed[index] ? false :
        matches_untrimmed(untrimmed, s, index + 1);
}



// The macro proper.
//
// There are several "simplifications" in this implementation, for the
// sake of brevity. First, we have only one viable option for declaring
// constexpr arrays: at namespace scope. This probably should be done
// two namespaces deep: one namespace that is likely to be unique for
// our little enum "library", then inside it a namespace whose name is
// based on the name of the enum to avoid collisions with other enums.
// I am using only one level of nesting.
//
// Declaring constexpr arrays inside the struct is not viable because
// they will need out-of-line definitions, which will result in
// duplicate symbols when linking. This can be solved with weak
// symbols, but that is compiler- and system-specific. It is not
// possible to declare constexpr arrays as static variables in
// constexpr functions due to the restrictions on such functions.
//
// Note that this prevents the use of this macro anywhere except at
// namespace scope. Ironically, the C++98 version of this, which can
// declare static arrays inside static member functions, is actually
// more flexible in this regard. It is shown in the CodeProject
// article.
//
// Second, for compilation performance reasons, it is best to separate
// the macro into a "parametric" portion, and the portion that depends
// on knowing __VA_ARGS__, and factor the former out into a template.
//
// Third, this code uses a default parameter in _from_string that may
// be better not exposed in the public interface.

#define ENUM(EnumName, Underlying, ...)                               \
namespace data_ ## EnumName {                                         \
    using _underlying = Underlying;                                   \
    enum { __VA_ARGS__ };                                             \
                                                                      \
    constexpr const size_t           _size =                          \
        IDENTITY(COUNT(__VA_ARGS__));                                 \
                                                                      \
    constexpr const _underlying      _values[] =                      \
        { IDENTITY(IGNORE_ASSIGN(__VA_ARGS__)) };                     \
                                                                      \
    constexpr const char * const     _raw_names[] =                   \
        { IDENTITY(STRINGIZE(__VA_ARGS__)) };                         \
}                                                                     \
                                                                      \
struct EnumName {                                                     \
    using _underlying = Underlying;                                   \
    enum _enum : _underlying { __VA_ARGS__ };                         \
                                                                      \
    const char * _to_string() const                                   \
    {                                                                 \
        for (size_t index = 0; index < data_ ## EnumName::_size;      \
             ++index) {                                               \
                                                                      \
            if (data_ ## EnumName::_values[index] == _value)          \
                return _trimmed_names()[index];                       \
        }                                                             \
                                                                      \
        throw std::runtime_error("invalid value");                    \
    }                                                                 \
                                                                      \
    constexpr static EnumName _from_string(const char *s,             \
                                           size_t index = 0)          \
    {                                                                 \
        return                                                        \
            index >= data_ ## EnumName::_size ?                       \
                    throw std::runtime_error("invalid identifier") :  \
            matches_untrimmed(                                        \
                data_ ## EnumName::_raw_names[index], s) ?            \
                    (EnumName)(_enum)data_ ## EnumName::_values[      \
                                                            index] :  \
            _from_string(s, index + 1);                               \
    }                                                                 \
                                                                      \
    EnumName() = delete;                                              \
    constexpr EnumName(_enum value) : _value(value) { }               \
    constexpr operator _enum() const { return (_enum)_value; }        \
                                                                      \
  private:                                                            \
    _underlying     _value;                                           \
                                                                      \
    static const char * const * _trimmed_names()                      \
    {                                                                 \
        static char     *the_names[data_ ## EnumName::_size];         \
        static bool     initialized = false;                          \
                                                                      \
        if (!initialized) {                                           \
            for (size_t index = 0; index < data_ ## EnumName::_size;  \
                 ++index) {                                           \
                                                                      \
                size_t  length =                                      \
                    std::strcspn(data_ ## EnumName::_raw_names[index],\
                                 terminators);                        \
                                                                      \
                the_names[index] = new char[length + 1];              \
                                                                      \
                std::strncpy(the_names[index],                        \
                             data_ ## EnumName::_raw_names[index],    \
                             length);                                 \
                the_names[index][length] = '\0';                      \
            }                                                         \
                                                                      \
            initialized = true;                                       \
        }                                                             \
                                                                      \
        return the_names;                                             \
    }                                                                 \
};

y

// The code above was a "header file". This is a program that uses it.
#include <iostream>
#include "the_file_above.h"

ENUM(Channel, char, Red = 1, Green, Blue)

constexpr Channel   channel = Channel::_from_string("Red");

int main()
{
    std::cout << channel._to_string() << std::endl;

    switch (channel) {
        case Channel::Red:   return 0;
        case Channel::Green: return 1;
        case Channel::Blue:  return 2;
    }
}

static_assert(sizeof(Channel) == sizeof(char), "");

El programa anterior se imprime Red, como era de esperar. Existe un cierto grado de seguridad de tipo, ya que no puede crear una enumeración sin inicializarla, y eliminar uno de los casos del switchresultado dará como resultado una advertencia del compilador (dependiendo de su compilador e indicadores). Además, tenga en cuenta que "Red"se convirtió en una enumeración durante la compilación.


Heya @mrhthepie, lamento que tu edición haya sido rechazada. Acabo de ver el correo electrónico al respecto. Voy a incorporarlo en la respuesta, ¡gracias por la corrección de errores!
antron

esto es genial. ¿Esto también funcionaría si quiero una enumeración de bits? Como si quisiera una enumeración de BitFlags, cada uno se 1Udesplaza en cierta cantidad.
user3240688

1
Parece que hay una pérdida de memoria _trimmed_names()en el código que publicó aquí ( new char[length + 1]pero no está configurado initializedcomo verdadero). ¿Me estoy perdiendo de algo? No veo el mismo problema en su código de Github.
user3240688

1
Está establecido en true, pero fuera de la iframa (pérdida de memoria detectada originalmente por @mrhthepie). Debería moverlo dentro ... Edición. Gracias por mirar de cerca este y el código GH.
antron

1
to_stringpodría devolver un string_viewC ++ 17, que no requiere terminación nula, y convertirse en constexpr.
Yakk - Adam Nevraumont

74

Para C ++ 17 C ++ 20, le interesará el trabajo del Reflection Study Group (SG7). Hay una serie paralela de documentos que cubren la redacción ( P0194 ) y la justificación, el diseño y la evolución ( P0385 ). (Los enlaces se resuelven al último artículo de cada serie).

A partir de P0194r2 (2016-10-15), la sintaxis usaría la reflexprpalabra clave propuesta :

meta::get_base_name_v<
  meta::get_element_m<
    meta::get_enumerators_m<reflexpr(MyEnum)>,
    0>
  >

Por ejemplo (adaptado de la rama reflexpr de clang de Matus Choclik ):

#include <reflexpr>
#include <iostream>

enum MyEnum { AAA = 1, BBB, CCC = 99 };

int main()
{
  auto name_of_MyEnum_0 = 
    std::meta::get_base_name_v<
      std::meta::get_element_m<
        std::meta::get_enumerators_m<reflexpr(MyEnum)>,
        0>
    >;

  // prints "AAA"
  std::cout << name_of_MyEnum_0 << std::endl;
}

La reflexión estática no logró entrar en C ++ 17 (más bien, en el borrador probablemente final presentado en la reunión de estándares de noviembre de 2016 en Issaquah) pero hay confianza en que llegará a C ++ 20; del informe de viaje de Herb Sutter :

En particular, el grupo de estudio de Reflexión revisó la última propuesta de reflexión estática fusionada y la encontró lista para ingresar a los principales grupos de Evolution en nuestra próxima reunión para comenzar a considerar la propuesta de reflexión estática unificada para un TS o para el próximo estándar.


2
@antron lo siento, tu edición fue rechazada; Lo habría aprobado si lo hubiera visto a tiempo. No había visto N4428, así que gracias por avisarme.
ecatmur

3
No hay problema, gracias por incorporarlo. Me pregunto por qué fue rechazado. Veo la razón "no lo hace más preciso", pero es claramente más preciso para el día de hoy.
antron

1
Gracias :-) He dividido el ejemplo final para evitar la barra de desplazamiento horizontal. Qué pena que el valor MyEnum::AAAno se pueda pasar como segundo argumento de std::meta::get_enumerators_m: - /
olibre

1
El hecho de que una tarea conceptual tan simple requiera 3 niveles de argumentos de plantilla anidados está muy sobredimensionado. Estoy seguro de que hay razones técnicas específicas para ello. Pero eso no significa que el resultado final sea fácil de usar. Me encanta C ++ y el código tiene sentido para mí. Pero el 90% de otros programadores con los que trabajo a diario evitan C ++ debido a un código como este. Estoy decepcionado de no haber visto soluciones más simples e integradas.
void.pointer

2
Parece que la estimación actual para la inclusión del próximo Reflection TS en el estándar es C ++ 23 : herbalutter.com/2018/04/02/…
Tim Rae

25

Esto es similar a Yuri Finkelstein; pero no requiere impulso. Estoy usando un mapa para que pueda asignar cualquier valor a las enumeraciones, cualquier orden.

Declaración de la clase enum como:

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);

El siguiente código creará automáticamente la clase enum y la sobrecarga:

  • '+' '+ =' para std :: string
  • '<<' para transmisiones
  • '~' solo para convertir a cadena (cualquier operador unario lo hará, pero personalmente no me gusta por claridad)
  • '*' para obtener el recuento de enumeraciones

No se requiere impulso, se proporcionan todas las funciones requeridas.

Código:

#include <algorithm>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

#define STRING_REMOVE_CHAR(str, ch) str.erase(std::remove(str.begin(), str.end(), ch), str.end())

std::vector<std::string> splitString(std::string str, char sep = ',') {
    std::vector<std::string> vecString;
    std::string item;

    std::stringstream stringStream(str);

    while (std::getline(stringStream, item, sep))
    {
        vecString.push_back(item);
    }

    return vecString;
}

#define DECLARE_ENUM_WITH_TYPE(E, T, ...)                                                                     \
    enum class E : T                                                                                          \
    {                                                                                                         \
        __VA_ARGS__                                                                                           \
    };                                                                                                        \
    std::map<T, std::string> E##MapName(generateEnumMap<T>(#__VA_ARGS__));                                    \
    std::ostream &operator<<(std::ostream &os, E enumTmp)                                                     \
    {                                                                                                         \
        os << E##MapName[static_cast<T>(enumTmp)];                                                            \
        return os;                                                                                            \
    }                                                                                                         \
    size_t operator*(E enumTmp) { (void) enumTmp; return E##MapName.size(); }                                 \
    std::string operator~(E enumTmp) { return E##MapName[static_cast<T>(enumTmp)]; }                          \
    std::string operator+(std::string &&str, E enumTmp) { return str + E##MapName[static_cast<T>(enumTmp)]; } \
    std::string operator+(E enumTmp, std::string &&str) { return E##MapName[static_cast<T>(enumTmp)] + str; } \
    std::string &operator+=(std::string &str, E enumTmp)                                                      \
    {                                                                                                         \
        str += E##MapName[static_cast<T>(enumTmp)];                                                           \
        return str;                                                                                           \
    }                                                                                                         \
    E operator++(E &enumTmp)                                                                                  \
    {                                                                                                         \
        auto iter = E##MapName.find(static_cast<T>(enumTmp));                                                 \
        if (iter == E##MapName.end() || std::next(iter) == E##MapName.end())                                  \
            iter = E##MapName.begin();                                                                        \
        else                                                                                                  \
        {                                                                                                     \
            ++iter;                                                                                           \
        }                                                                                                     \
        enumTmp = static_cast<E>(iter->first);                                                                \
        return enumTmp;                                                                                       \
    }                                                                                                         \
    bool valid##E(T value) { return (E##MapName.find(value) != E##MapName.end()); }

#define DECLARE_ENUM(E, ...) DECLARE_ENUM_WITH_TYPE(E, int32_t, __VA_ARGS__)
template <typename T>
std::map<T, std::string> generateEnumMap(std::string strMap)
{
    STRING_REMOVE_CHAR(strMap, ' ');
    STRING_REMOVE_CHAR(strMap, '(');

    std::vector<std::string> enumTokens(splitString(strMap));
    std::map<T, std::string> retMap;
    T inxMap;

    inxMap = 0;
    for (auto iter = enumTokens.begin(); iter != enumTokens.end(); ++iter)
    {
        // Token: [EnumName | EnumName=EnumValue]
        std::string enumName;
        T enumValue;
        if (iter->find('=') == std::string::npos)
        {
            enumName = *iter;
        }
        else
        {
            std::vector<std::string> enumNameValue(splitString(*iter, '='));
            enumName = enumNameValue[0];
            //inxMap = static_cast<T>(enumNameValue[1]);
            if (std::is_unsigned<T>::value)
            {
                inxMap = static_cast<T>(std::stoull(enumNameValue[1], 0, 0));
            }
            else
            {
                inxMap = static_cast<T>(std::stoll(enumNameValue[1], 0, 0));
            }
        }
        retMap[inxMap++] = enumName;
    }

    return retMap;
}

Ejemplo:

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);

int main(void) {
    TestEnumClass first, second;
    first = TestEnumClass::FOUR;
    second = TestEnumClass::TWO;

    std::cout << first << "(" << static_cast<uint32_t>(first) << ")" << std::endl; // FOUR(4)

    std::string strOne;
    strOne = ~first;
    std::cout << strOne << std::endl; // FOUR

    std::string strTwo;
    strTwo = ("Enum-" + second) + (TestEnumClass::THREE + "-test");
    std::cout << strTwo << std::endl; // Enum-TWOTHREE-test

    std::string strThree("TestEnumClass: ");
    strThree += second;
    std::cout << strThree << std::endl; // TestEnumClass: TWO
    std::cout << "Enum count=" << *first << std::endl;
}

You can run the code here


1
¿Podemos tener saltos de línea dentro de esta definición de macro?
einpoklum

1
*Agregué la sobrecarga para obtener el recuento de enumeraciones ... Espero que no te
importe

1
¿Hay alguna razón por la cual esta implementación usa std::map(O (log (n)) indexación) en lugar de std::unordered_map(O (1) indexación)?
River Tam

1
Además, creo que los métodos deben estar marcados inlinepara que pueda declarar enumeraciones en los archivos de encabezado como normal sin obtener errores de "definición múltiple de" desde el vinculador. (aunque no estoy seguro si esa es realmente la solución más limpia / mejor)
River Tam

1
(perdón por el spam pero parece que no puedo editar los comentarios hoy) hay otros problemas con esto en un archivo de encabezado. El mapa ( E##MapName) necesita moverse a una unidad de compilación que también tenga acceso a la enumeración. He creado una solución, pero no es muy limpia y tendría que obtener permiso para compartirla. Por ahora, solo estoy comentando para decir que no tiene sentido marcar los métodos en línea sin las características adicionales necesarias para admitir el uso en un archivo de encabezado.
River Tam

20

En 2011, pasé un fin de semana ajustando una solución basada en macro y terminé sin usarla nunca.

Mi procedimiento actual es iniciar Vim, copiar los enumeradores en un cuerpo de conmutador vacío, iniciar una nueva macro, transformar el primer enumerador en una declaración de caso, mover el cursor al comienzo de la siguiente línea, detener la macro y generar el caso restante declaraciones ejecutando la macro en los otros enumeradores.

Las macros de Vim son más divertidas que las macros de C ++.

Ejemplo de la vida real:

enum class EtherType : uint16_t
{
    ARP   = 0x0806,
    IPv4  = 0x0800,
    VLAN  = 0x8100,
    IPv6  = 0x86DD
};

Crearé esto:

std::ostream& operator<< (std::ostream& os, EtherType ethertype)
{
    switch (ethertype)
    {
        case EtherType::ARP : return os << "ARP" ;
        case EtherType::IPv4: return os << "IPv4";
        case EtherType::VLAN: return os << "VLAN";
        case EtherType::IPv6: return os << "IPv6";
        // omit default case to trigger compiler warning for missing cases
    };
    return os << static_cast<std::uint16_t>(ethertype);
}

Y así es como me las arreglo.

Sin embargo, el soporte nativo para la cadena de enumeración sería mucho mejor. Estoy muy interesado en ver los resultados del grupo de trabajo de reflexión en C ++ 17.

@Sehe publicó una forma alternativa de hacerlo en los comentarios .


1
Yo hago exactamente esto. Aunque generalmente uso Surround vim y bloqueo las selecciones en el camino
vea el

@sehe Interesante. Debería echar un vistazo a "surround" porque actualmente necesito muchas pulsaciones de teclas.
StackedCrooked

Aquí está en sangrienta, sin macros (a menos que .cuente): i.imgur.com/gY4ZhBE.gif
sehe

1
El gif animado es lindo, pero es difícil saber cuándo comienza y termina, y qué tan lejos estamos. ... en realidad, tacha eso, no es lindo, es una distracción. Yo digo matarlo.
einpoklum

Este enfoque de selección de bloque en vim es agradable y todo, pero ¿por qué no simplemente usar algo como :'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/?
Ruslan

14

No sé si le va a gustar esto o no, no estoy muy contento con esta solución, pero es un enfoque amigable de C ++ 14 porque está usando variables de plantilla y abusando de la especialización de plantilla:

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

template<MyEnum> const char MyEnumName[] = "Invalid MyEnum value";
template<> const char MyEnumName<MyEnum::AAA>[] = "AAA";
template<> const char MyEnumName<MyEnum::BBB>[] = "BBB";
template<> const char MyEnumName<MyEnum::CCC>[] = "CCC";

int main()
{
    // Prints "AAA"
    std::cout << MyEnumName<MyEnum::AAA> << '\n';
    // Prints "Invalid MyEnum value"
    std::cout << MyEnumName<static_cast<MyEnum>(0x12345678)> << '\n';
    // Well... in fact it prints "Invalid MyEnum value" for any value
    // different of MyEnum::AAA, MyEnum::BBB or MyEnum::CCC.

    return 0;
}

Lo peor de este enfoque es que es un dolor de mantenimiento, pero también es un dolor mantener algunos de otros enfoques similares, ¿no es así?

Buenos puntos sobre este enfoque:

  • Uso de plantillas variables (función C ++ 14)
  • Con la especialización de plantilla podemos "detectar" cuando se utiliza un valor no válido (pero no estoy seguro de si esto podría ser útil).
  • Se ve bien.
  • La búsqueda de nombres se realiza en tiempo de compilación.

Live example

Editar

Misteriosa user673679 que tienes razón; el enfoque de plantilla variable C ++ 14 no maneja el caso de tiempo de ejecución, fue mi culpa olvidarlo :(

Pero aún podemos usar algunas características modernas de C ++ y una plantilla variable más trucos de plantillas variables para lograr una traducción en tiempo de ejecución del valor enum a la cadena ... es tan molesto como los otros, pero vale la pena mencionarlo.

Comencemos a usar un alias de plantilla para acortar el acceso a un mapa de enumeración a cadena:

// enum_map contains pairs of enum value and value string for each enum
// this shortcut allows us to use enum_map<whatever>.
template <typename ENUM>
using enum_map = std::map<ENUM, const std::string>;

// This variable template will create a map for each enum type which is
// instantiated with.
template <typename ENUM>
enum_map<ENUM> enum_values{};

Luego, el truco de plantilla variadic:

template <typename ENUM>
void initialize() {}

template <typename ENUM, typename ... args>
void initialize(const ENUM value, const char *name, args ... tail)
{
    enum_values<ENUM>.emplace(value, name);
    initialize<ENUM>(tail ...);
}

El " mejor truco " aquí es el uso de una plantilla variable para el mapa que contiene los valores y nombres de cada entrada de enumeración; este mapa será el mismo en cada unidad de traducción y tendrá el mismo nombre en todas partes, por lo que es bastante sencillo y ordenado, si llamamos a la initializefunción así:

initialize
(
    MyEnum::AAA, "AAA",
    MyEnum::BBB, "BBB",
    MyEnum::CCC, "CCC"
);

Estamos asignando nombres a cada MyEnumentrada y se pueden usar en tiempo de ejecución:

std::cout << enum_values<MyEnum>[MyEnum::AAA] << '\n';

Pero se puede mejorar con SFINAE y el <<operador de sobrecarga :

template<typename ENUM, class = typename std::enable_if<std::is_enum<ENUM>::value>::type>
std::ostream &operator <<(std::ostream &o, const ENUM value)
{
    static const std::string Unknown{std::string{typeid(ENUM).name()} + " unknown value"};
    auto found = enum_values<ENUM>.find(value);

    return o << (found == enum_values<ENUM>.end() ? Unknown : found->second);
}

Con el correcto operator <<ahora podemos usar la enumeración de esta manera:

std::cout << MyEnum::AAA << '\n';

Esto también es molesto de mantener y se puede mejorar, pero espero que entiendas la idea.

Live example


Esto se ve bastante ordenado (¿es posible simplemente no definir la variable no especializada?). Tal vez me estoy perdiendo algo, ya que no veo cómo maneja el caso de tiempo de ejecución.
user673679

@Paula_plus_plus: ¿No deberías usar un std::arraymapa en lugar del difícil de manejar? Solo será preferible para las enumeraciones a partir de ... ¿qué, 2 ^ 10 valores? Quizás aún más.
einpoklum

@einpoklum sería increíble si pudiéramos asegurar en tiempo de ejecución cuántos elementos enumtiene. Desafortunadamente, no podemos. Y todo el objetivo del mapa es solo asociar nombres con valores, que es lo que std::mapes bueno.
PaperBirdMaster

@Paula_plus_plus: ya está llamando a una initialize()función cuyo número de argumentos es el número de valores de enumeración, por lo que sabe el número de valores en tiempo de compilación. Solo se conoce el valor específico que se le pide que imprima en tiempo de ejecución. Además, incluso si no conociera ese número, un std :: vector sería más rápido que un std :: map, nuevamente, en casi todos los casos realistas.
einpoklum

@einpoklum es un muy buen punto, lo pensaré, ¡gracias! Lo único que me preocupa es que std::arrayno es un contenedor de valores clave y, por lo tanto, carece de métodos de búsqueda; de todos modos lo pensaré.
PaperBirdMaster

7

Si tu enumparece

enum MyEnum
{
  AAA = -8,
  BBB = '8',
  CCC = AAA + BBB
};

Puede mover el contenido de enuma un nuevo archivo:

AAA = -8,
BBB = '8',
CCC = AAA + BBB

Y luego los valores pueden estar rodeados por una macro:

// default definition
#ifned ITEM(X,Y)
#define ITEM(X,Y)
#endif

// Items list
ITEM(AAA,-8)
ITEM(BBB,'8')
ITEM(CCC,AAA+BBB)

// clean up
#undef ITEM

El siguiente paso puede incluir los elementos en el enumnuevo:

enum MyEnum
{
  #define ITEM(X,Y) X=Y,
  #include "enum_definition_file"
};

Y finalmente puede generar funciones de utilidad sobre esto enum:

std::string ToString(MyEnum value)
{
  switch( value )
  {
    #define ITEM(X,Y) case X: return #X;
    #include "enum_definition_file"
  }

  return "";
}

MyEnum FromString(std::string const& value)
{
  static std::map<std::string,MyEnum> converter
  {
    #define ITEM(X,Y) { #X, X },
    #include "enum_definition_file"
  };

  auto it = converter.find(value);
  if( it != converter.end() )
    return it->second;
  else
    throw std::runtime_error("Value is missing");
}

La solución se puede aplicar a estándares C ++ anteriores y no utiliza elementos modernos de C ++, pero se puede usar para generar gran cantidad de código sin demasiado esfuerzo y mantenimiento.


3
No hay necesidad de un archivo separado. Esto es esencialmente una macro x .
HolyBlackCat

@HolyBlackCat si divide la solución en algunos archivos, puede reutilizar los valores de enumeración para diferentes propósitos
eferion

Estoy tratando de decirle que puede hacer lo mismo si coloca la lista de valores en una sola macro junto con la definición de enumeración en un encabezado.
HolyBlackCat

@HolyBlackCat sí, te entiendo, pero prefiero esta solución. Por otro lado, esta solución se puede encontrar en el código fuente clang, por lo que creo que es una buena manera de resolver el problema
Eferion

Lo suficientemente justo. Supongo que no debería haber rechazado esto, ya que de hecho puede tener algunos usos. (Perdón por la edición ficticia, el sistema bloquea mi voto de lo contrario.)
HolyBlackCat

6

Tuve el mismo problema hace un par de días. No pude encontrar ninguna solución de C ++ sin un poco de magia mágica extraña, así que decidí escribir un generador de código CMake para generar declaraciones de casos de cambio simples.

Uso:

enum2str_generate(
  PATH          <path to place the files in>
  CLASS_NAME    <name of the class (also prefix for the files)>
  FUNC_NAME     <name of the (static) member function>
  NAMESPACE     <the class will be inside this namespace>
  INCLUDES      <LIST of files where the enums are defined>
  ENUMS         <LIST of enums to process>
  BLACKLIST     <LIST of constants to ignore>
  USE_CONSTEXPR <whether to use constexpr or not (default: off)>
  USE_C_STRINGS <whether to use c strings instead of std::string or not (default: off)>
)

La función busca los archivos de inclusión en el sistema de archivos (usa los directorios de inclusión provistos con el comando include_directories), los lee y realiza algunas expresiones regulares para generar la clase y las funciones.

NOTA: constexpr implica en línea en C ++, por lo que usar la opción USE_CONSTEXPR generará una clase de solo encabezado.

Ejemplo:

./include/ah:

enum AAA : char { A1, A2 };

typedef enum {
   VAL1          = 0,
   VAL2          = 1,
   VAL3          = 2,
   VAL_FIRST     = VAL1,    // Ignored
   VAL_LAST      = VAL3,    // Ignored
   VAL_DUPLICATE = 1,       // Ignored
   VAL_STRANGE   = VAL2 + 1 // Must be blacklisted
} BBB;

./CMakeLists.txt:

include_directories( ${PROJECT_SOURCE_DIR}/includes ...)

enum2str_generate(
   PATH       "${PROJECT_SOURCE_DIR}"
   CLASS_NAME "enum2Str"
   NAMESPACE  "abc"
   FUNC_NAME  "toStr"
   INCLUDES   "a.h" # WITHOUT directory
   ENUMS      "AAA" "BBB"
   BLACKLIST  "VAL_STRANGE")

Genera:

./enum2Str.hpp:

/*!
  * \file enum2Str.hpp
  * \warning This is an automatically generated file!
  */

#ifndef ENUM2STR_HPP
#define ENUM2STR_HPP

#include <string>
#include <a.h>

namespace abc {

class enum2Str {
 public:
   static std::string toStr( AAA _var ) noexcept;
   static std::string toStr( BBB _var ) noexcept;
};

}

#endif // ENUM2STR_HPP

./enum2Str.cpp:

/*!
  * \file enum2Str.cpp
  * \warning This is an automatically generated file!
  */

#include "enum2Str.hpp"

namespace abc {

/*!
 * \brief Converts the enum AAA to a std::string
 * \param _var The enum value to convert
 * \returns _var converted to a std::string
 */
std::string enum2Str::toStr( AAA _var ) noexcept {
   switch ( _var ) {
      case A1: return "A1";
      case A2: return "A2";
      default: return "<UNKNOWN>";
   }
}

/*!
 * \brief Converts the enum BBB to a std::string
 * \param _var The enum value to convert
 * \returns _var converted to a std::string
 */
std::string enum2Str::toStr( BBB _var ) noexcept {
   switch ( _var ) {
      case VAL1: return "VAL1";
      case VAL2: return "VAL2";
      case VAL3: return "VAL3";
      default: return "<UNKNOWN>";
   }
}
}

Actualizar:

El script ahora también admite enumeraciones de ámbito (enum class | struct) y lo moví a un repositorio separado con algunos otros scripts que uso a menudo: https://github.com/mensinda/cmakeBuildTools


¡Guauu! Idea muy original e innovadora :-) espero que tenga el valor para actualizar su generador con el fin de proporcionar una constexpry noexceptla versión ;-) He también se limitó su proyecto GitHub ;-) Saludos
olibre

1
Actualizado el generador. Las funciones ahora serán siempre constexpr y enum: <type> ahora es compatible. Gracias por la estrella :)
Mense

El enlace está roto ... -.-
yeoman

El enlace ahora está arreglado.
Mense

4

Solo genera tus enumeraciones. Escribir un generador para ese propósito es aproximadamente cinco minutos de trabajo.

Código generador en java y python, súper fácil de portar a cualquier lenguaje que desee, incluido C ++.

También súper fácil de extender por cualquier funcionalidad que desee.

entrada de ejemplo:

First = 5
Second
Third = 7
Fourth
Fifth=11

encabezado generado:

#include <iosfwd>

enum class Hallo
{
    First = 5,
    Second = 6,
    Third = 7,
    Fourth = 8,
    Fifth = 11
};

std::ostream & operator << (std::ostream &, const Hallo&);

archivo cpp generado

#include <ostream>

#include "Hallo.h"

std::ostream & operator << (std::ostream &out, const Hallo&value)
{
    switch(value)
    {
    case Hallo::First:
        out << "First";
        break;
    case Hallo::Second:
        out << "Second";
        break;
    case Hallo::Third:
        out << "Third";
        break;
    case Hallo::Fourth:
        out << "Fourth";
        break;
    case Hallo::Fifth:
        out << "Fifth";
        break;
    default:
        out << "<unknown>";
    }

    return out;
}

Y el generador, en una forma muy concisa como plantilla para portabilidad y extensión. Este código de ejemplo realmente trata de evitar sobrescribir cualquier archivo, pero aún lo usa bajo su propio riesgo.

package cppgen;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class EnumGenerator
{
    static void fail(String message)
    {
        System.err.println(message);
        System.exit(1);
    }

    static void run(String[] args)
    throws Exception
    {
        Pattern pattern = Pattern.compile("\\s*(\\w+)\\s*(?:=\\s*(\\d+))?\\s*", Pattern.UNICODE_CHARACTER_CLASS);
        Charset charset = Charset.forName("UTF8");
        String tab = "    ";

        if (args.length != 3)
        {
            fail("Required arguments: <enum name> <input file> <output dir>");
        }

        String enumName = args[0];

        File inputFile = new File(args[1]);

        if (inputFile.isFile() == false)
        {
            fail("Not a file: [" + inputFile.getCanonicalPath() + "]");
        }

        File outputDir = new File(args[2]);

        if (outputDir.isDirectory() == false)
        {
            fail("Not a directory: [" + outputDir.getCanonicalPath() + "]");
        }

        File headerFile = new File(outputDir, enumName + ".h");
        File codeFile = new File(outputDir, enumName + ".cpp");

        for (File file : new File[] { headerFile, codeFile })
        {
            if (file.exists())
            {
                fail("Will not overwrite file [" + file.getCanonicalPath() + "]");
            }
        }

        int nextValue = 0;

        Map<String, Integer> fields = new LinkedHashMap<>();

        try
        (
            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), charset));
        )
        {
            while (true)
            {
                String line = reader.readLine();

                if (line == null)
                {
                    break;
                }

                if (line.trim().length() == 0)
                {
                    continue;
                }

                Matcher matcher = pattern.matcher(line);

                if (matcher.matches() == false)
                {
                    fail("Syntax error: [" + line + "]");
                }

                String fieldName = matcher.group(1);

                if (fields.containsKey(fieldName))
                {
                    fail("Double fiend name: " + fieldName);
                }

                String valueString = matcher.group(2);

                if (valueString != null)
                {
                    int value = Integer.parseInt(valueString);

                    if (value < nextValue)
                    {
                        fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName);
                    }

                    nextValue = value;
                }

                fields.put(fieldName, nextValue);

                ++nextValue;
            }
        }

        try
        (
            PrintWriter headerWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(headerFile), charset));
            PrintWriter codeWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(codeFile), charset));
        )
        {
            headerWriter.println();
            headerWriter.println("#include <iosfwd>");
            headerWriter.println();
            headerWriter.println("enum class " + enumName);
            headerWriter.println('{');
            boolean first = true;
            for (Entry<String, Integer> entry : fields.entrySet())
            {
                if (first == false)
                {
                    headerWriter.println(",");
                }

                headerWriter.print(tab + entry.getKey() + " = " + entry.getValue());

                first = false;
            }
            if (first == false)
            {
                headerWriter.println();
            }
            headerWriter.println("};");
            headerWriter.println();
            headerWriter.println("std::ostream & operator << (std::ostream &, const " + enumName + "&);");
            headerWriter.println();

            codeWriter.println();
            codeWriter.println("#include <ostream>");
            codeWriter.println();
            codeWriter.println("#include \"" + enumName + ".h\"");
            codeWriter.println();
            codeWriter.println("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)");
            codeWriter.println('{');
            codeWriter.println(tab + "switch(value)");
            codeWriter.println(tab + '{');
            first = true;
            for (Entry<String, Integer> entry : fields.entrySet())
            {
                codeWriter.println(tab + "case " + enumName + "::" + entry.getKey() + ':');
                codeWriter.println(tab + tab + "out << \"" + entry.getKey() + "\";");
                codeWriter.println(tab + tab + "break;");

                first = false;
            }
            codeWriter.println(tab + "default:");
            codeWriter.println(tab + tab + "out << \"<unknown>\";");
            codeWriter.println(tab + '}');
            codeWriter.println();
            codeWriter.println(tab + "return out;");
            codeWriter.println('}');
            codeWriter.println();
        }
    }

    public static void main(String[] args)
    {
        try
        {
            run(args);
        }
        catch(Exception exc)
        {
            exc.printStackTrace();
            System.exit(1);
        }
    }
}

Y un puerto para Python 3.5 porque es lo suficientemente diferente como para ser potencialmente útil

import re
import collections
import sys
import io
import os

def fail(*args):
    print(*args)
    exit(1)

pattern = re.compile(r'\s*(\w+)\s*(?:=\s*(\d+))?\s*')
tab = "    "

if len(sys.argv) != 4:
    n=0
    for arg in sys.argv:
        print("arg", n, ":", arg, " / ", sys.argv[n])
        n += 1
    fail("Required arguments: <enum name> <input file> <output dir>")

enumName = sys.argv[1]

inputFile = sys.argv[2]

if not os.path.isfile(inputFile):
    fail("Not a file: [" + os.path.abspath(inputFile) + "]")

outputDir = sys.argv[3]

if not os.path.isdir(outputDir):
    fail("Not a directory: [" + os.path.abspath(outputDir) + "]")

headerFile = os.path.join(outputDir, enumName + ".h")
codeFile = os.path.join(outputDir, enumName + ".cpp")

for file in [ headerFile, codeFile ]:
    if os.path.exists(file):
        fail("Will not overwrite file [" + os.path.abspath(file) + "]")

nextValue = 0

fields = collections.OrderedDict()

for line in open(inputFile, 'r'):
    line = line.strip()

    if len(line) == 0:
        continue

    match = pattern.match(line)

    if match == None:
        fail("Syntax error: [" + line + "]")

    fieldName = match.group(1)

    if fieldName in fields:
        fail("Double field name: " + fieldName)

    valueString = match.group(2)

    if valueString != None:
        value = int(valueString)

        if value < nextValue:
            fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName)

        nextValue = value

    fields[fieldName] = nextValue

    nextValue += 1

headerWriter = open(headerFile, 'w')
codeWriter = open(codeFile, 'w')

try:
    headerWriter.write("\n")
    headerWriter.write("#include <iosfwd>\n")
    headerWriter.write("\n")
    headerWriter.write("enum class " + enumName + "\n")
    headerWriter.write("{\n")
    first = True
    for fieldName, fieldValue in fields.items():
        if not first:
            headerWriter.write(",\n")

        headerWriter.write(tab + fieldName + " = " + str(fieldValue))

        first = False
    if not first:
        headerWriter.write("\n")
    headerWriter.write("};\n")
    headerWriter.write("\n")
    headerWriter.write("std::ostream & operator << (std::ostream &, const " + enumName + "&);\n")
    headerWriter.write("\n")

    codeWriter.write("\n")
    codeWriter.write("#include <ostream>\n")
    codeWriter.write("\n")
    codeWriter.write("#include \"" + enumName + ".h\"\n")
    codeWriter.write("\n")
    codeWriter.write("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)\n")
    codeWriter.write("{\n")
    codeWriter.write(tab + "switch(value)\n")
    codeWriter.write(tab + "{\n")
    for fieldName in fields.keys():
        codeWriter.write(tab + "case " + enumName + "::" + fieldName + ":\n")
        codeWriter.write(tab + tab + "out << \"" + fieldName + "\";\n")
        codeWriter.write(tab + tab + "break;\n")
    codeWriter.write(tab + "default:\n")
    codeWriter.write(tab + tab + "out << \"<unknown>\";\n")
    codeWriter.write(tab + "}\n")
    codeWriter.write("\n")
    codeWriter.write(tab + "return out;\n")
    codeWriter.write("}\n")
    codeWriter.write("\n")
finally:
    headerWriter.close()
    codeWriter.close()

1
Muchas gracias por compartir su generador en dos idiomas :-) ¿Pero tiene alguna idea de cómo generar en tiempo de compilación? Por ejemplo, ¿podemos imaginar traducir su generador utilizando declaraciones CMake para actualizar el código generado por C ++ cuando se modifican los datos de entrada? Mi sueño es obligar al compilador de C ++ a generar enumeraciones en la compilación utilizando metaprogramación ( variadic template classy constexprfunciones).
olibre

Otoh, en caso de que sea demasiado engorroso agregar un comando cmake personalizado, puede automatizar su IDE o llamar al gererator manualmente y tener la salida en control de fuente. A veces es una buena idea haber generado código en el control de origen de todos modos, siempre y cuando no sea demasiado, y la gente entienda que no se supone que hagan cambios manuales, porque a veces es interesante mirar el historial de los archivos generados cuando estamos depurando algo extraño y tengo la sospecha de que un cambio reciente en el generador puede haber roto algo :)
yeoman

Acerca de generar cosas en tiempo de compilación, eso es muy fácil en LISP porque la sintaxis es extremadamente limpia y fácil. Esto se ve ayudado por el hecho de que está tipificado dinámicamente, lo que le permite ser conciso y legible sin mucha sintaxis. El equivalente de las macros LISP en C ++ necesitaría una forma muy complicada de describir el AST de lo que está intentando generar. Y un AST para C ++ nunca es bonito :(
yeoman

Directamente en Make en lugar de cmake, es muy fácil por cierto. Simplemente genere los objetivos .h y .cpp para cada archivo .enum a través de find, y haga que estos objetivos dependan de dichos enum defs, para que se vuelvan a generar automáticamente una vez que los archivos .enum def cambien. Probablemente sea mucho más fácil en cmake porque está lleno de magia para este tipo de cosas, pero uso regularmente Make, ant y gradle, pero solo tengo un conocimiento limitado de Maven, cmake y gruñir :)
yeoman

Gracias por su respuesta :-) Creo que la mayoría de los desarrolladores de C ++ apreciarán si su generador puede detectar enumeraciones directamente dentro del código de C ++ como enum class Hallo{ First=5, Second=6, Third=7, Fourth=8};o en varias líneas :-D ¿Cree que puede adaptar su generador para detectar un enumdentro de un C ++? ¿archivo? Lo mejor podría ser generar código solo al detectar una etiqueta como /*<Generate enum to string here>*/. Luego, su generador escribe en el lugar el código generado C ++ correspondiente (reemplazando el código generado anterior). ^ _ ^ ¿Qué generador tan impresionante no es? Saludos :-)
olibre

3

Según la solicitud del OP, aquí hay una versión simplificada de la desagradable solución de macro basada en Boost Preprosessor y Variadic Macros .

Permite una lista simple como la sintaxis de los elementos del enumerador junto con la configuración de valores para elementos específicos para que

XXX_ENUM(foo,(a,b,(c,42)));

se expande a

enum foo {
    a,
    b,
    c=42
};

Junto con las funciones necesarias para generar y volver a convertir. Esta macro ha existido por años, y no estoy totalmente seguro de que sea la forma más eficiente, o que sea una forma conforme, pero desde entonces ha estado funcionando

El código completo se puede ver en acción tanto en Ideone como en Coliru .

Su fealdad gigantesca está arriba; Lo hubiera puesto detrás de los spoilers para proteger tus ojos, si supiera cómo, pero a Markdown no le gusto.

La biblioteca (fusionada en un solo archivo de encabezado)

#include <boost/preprocessor.hpp>
#include <string>
#include <unordered_map>

namespace xxx
{

template<class T>
struct enum_cast_adl_helper { };

template<class E>
E enum_cast( const std::string& s )
{
    return do_enum_cast(s,enum_cast_adl_helper<E>());
}

template<class E>
E enum_cast( const char* cs )
{
    std::string s(cs);
    return enum_cast<E>(s);
}

} // namespace xxx

#define XXX_PP_ARG_N(                             \
          _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
         _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
         _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
         _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
         _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
         _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
         _61,_62,_63,N,...) N

#define XXX_PP_RSEQ_N()                 \
         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,0 

#define XXX_PP_NARG_(...) XXX_PP_ARG_N(__VA_ARGS__)
#define XXX_PP_NARG(...)  XXX_PP_NARG_(__VA_ARGS__,XXX_PP_RSEQ_N())
#define XXX_TUPLE_SIZE_INTERNAL(TUPLE) XXX_PP_NARG TUPLE

#define XXX_TUPLE_CHOICE(i)                            \
  BOOST_PP_APPLY(                                      \
    BOOST_PP_TUPLE_ELEM(                               \
      25, i, (                                         \
        (0), (1), (2), (3), (4), (5), (6), (7), (8),   \
        (9), (10), (11), (12), (13), (14), (15), (16), \
        (17), (18), (19), (20), (21), (22), (23), (24) \
  ) ) )

#define BOOST_PP_BOOL_00  BOOST_PP_BOOL_0
#define BOOST_PP_BOOL_01  BOOST_PP_BOOL_1
#define BOOST_PP_BOOL_02  BOOST_PP_BOOL_2
#define BOOST_PP_BOOL_03  BOOST_PP_BOOL_3
#define BOOST_PP_BOOL_04  BOOST_PP_BOOL_4
#define BOOST_PP_BOOL_05  BOOST_PP_BOOL_5
#define BOOST_PP_BOOL_06  BOOST_PP_BOOL_6
#define BOOST_PP_BOOL_07  BOOST_PP_BOOL_7
#define BOOST_PP_BOOL_08  BOOST_PP_BOOL_8
#define BOOST_PP_BOOL_09  BOOST_PP_BOOL_9
#define BOOST_PP_BOOL_010 BOOST_PP_BOOL_10
#define BOOST_PP_BOOL_011 BOOST_PP_BOOL_11
#define BOOST_PP_BOOL_012 BOOST_PP_BOOL_12
#define BOOST_PP_BOOL_013 BOOST_PP_BOOL_13
#define BOOST_PP_BOOL_014 BOOST_PP_BOOL_14
#define BOOST_PP_BOOL_015 BOOST_PP_BOOL_15
#define BOOST_PP_BOOL_016 BOOST_PP_BOOL_16
#define BOOST_PP_BOOL_017 BOOST_PP_BOOL_17
#define BOOST_PP_BOOL_018 BOOST_PP_BOOL_18
#define BOOST_PP_BOOL_019 BOOST_PP_BOOL_19
#define BOOST_PP_BOOL_020 BOOST_PP_BOOL_20
#define BOOST_PP_BOOL_021 BOOST_PP_BOOL_21
#define BOOST_PP_BOOL_022 BOOST_PP_BOOL_22
#define BOOST_PP_BOOL_023 BOOST_PP_BOOL_23
#define BOOST_PP_BOOL_024 BOOST_PP_BOOL_24
#define BOOST_PP_BOOL_025 BOOST_PP_BOOL_25
#define BOOST_PP_BOOL_026 BOOST_PP_BOOL_26
#define BOOST_PP_BOOL_027 BOOST_PP_BOOL_27
#define BOOST_PP_BOOL_028 BOOST_PP_BOOL_28
#define BOOST_PP_BOOL_029 BOOST_PP_BOOL_29
#define BOOST_PP_BOOL_030 BOOST_PP_BOOL_30
#define BOOST_PP_BOOL_031 BOOST_PP_BOOL_31
#define BOOST_PP_BOOL_032 BOOST_PP_BOOL_32
#define BOOST_PP_BOOL_033 BOOST_PP_BOOL_33
#define BOOST_PP_BOOL_034 BOOST_PP_BOOL_34
#define BOOST_PP_BOOL_035 BOOST_PP_BOOL_35
#define BOOST_PP_BOOL_036 BOOST_PP_BOOL_36
#define BOOST_PP_BOOL_037 BOOST_PP_BOOL_37
#define BOOST_PP_BOOL_038 BOOST_PP_BOOL_38
#define BOOST_PP_BOOL_039 BOOST_PP_BOOL_39
#define BOOST_PP_BOOL_040 BOOST_PP_BOOL_40
#define BOOST_PP_BOOL_041 BOOST_PP_BOOL_41
#define BOOST_PP_BOOL_042 BOOST_PP_BOOL_42
#define BOOST_PP_BOOL_043 BOOST_PP_BOOL_43
#define BOOST_PP_BOOL_044 BOOST_PP_BOOL_44
#define BOOST_PP_BOOL_045 BOOST_PP_BOOL_45
#define BOOST_PP_BOOL_046 BOOST_PP_BOOL_46
#define BOOST_PP_BOOL_047 BOOST_PP_BOOL_47
#define BOOST_PP_BOOL_048 BOOST_PP_BOOL_48
#define BOOST_PP_BOOL_049 BOOST_PP_BOOL_49
#define BOOST_PP_BOOL_050 BOOST_PP_BOOL_50
#define BOOST_PP_BOOL_051 BOOST_PP_BOOL_51
#define BOOST_PP_BOOL_052 BOOST_PP_BOOL_52
#define BOOST_PP_BOOL_053 BOOST_PP_BOOL_53
#define BOOST_PP_BOOL_054 BOOST_PP_BOOL_54
#define BOOST_PP_BOOL_055 BOOST_PP_BOOL_55
#define BOOST_PP_BOOL_056 BOOST_PP_BOOL_56
#define BOOST_PP_BOOL_057 BOOST_PP_BOOL_57
#define BOOST_PP_BOOL_058 BOOST_PP_BOOL_58
#define BOOST_PP_BOOL_059 BOOST_PP_BOOL_59
#define BOOST_PP_BOOL_060 BOOST_PP_BOOL_60
#define BOOST_PP_BOOL_061 BOOST_PP_BOOL_61
#define BOOST_PP_BOOL_062 BOOST_PP_BOOL_62
#define BOOST_PP_BOOL_063 BOOST_PP_BOOL_63

#define BOOST_PP_DEC_00  BOOST_PP_DEC_0
#define BOOST_PP_DEC_01  BOOST_PP_DEC_1
#define BOOST_PP_DEC_02  BOOST_PP_DEC_2
#define BOOST_PP_DEC_03  BOOST_PP_DEC_3
#define BOOST_PP_DEC_04  BOOST_PP_DEC_4
#define BOOST_PP_DEC_05  BOOST_PP_DEC_5
#define BOOST_PP_DEC_06  BOOST_PP_DEC_6
#define BOOST_PP_DEC_07  BOOST_PP_DEC_7
#define BOOST_PP_DEC_08  BOOST_PP_DEC_8
#define BOOST_PP_DEC_09  BOOST_PP_DEC_9
#define BOOST_PP_DEC_010 BOOST_PP_DEC_10
#define BOOST_PP_DEC_011 BOOST_PP_DEC_11
#define BOOST_PP_DEC_012 BOOST_PP_DEC_12
#define BOOST_PP_DEC_013 BOOST_PP_DEC_13
#define BOOST_PP_DEC_014 BOOST_PP_DEC_14
#define BOOST_PP_DEC_015 BOOST_PP_DEC_15
#define BOOST_PP_DEC_016 BOOST_PP_DEC_16
#define BOOST_PP_DEC_017 BOOST_PP_DEC_17
#define BOOST_PP_DEC_018 BOOST_PP_DEC_18
#define BOOST_PP_DEC_019 BOOST_PP_DEC_19
#define BOOST_PP_DEC_020 BOOST_PP_DEC_20
#define BOOST_PP_DEC_021 BOOST_PP_DEC_21
#define BOOST_PP_DEC_022 BOOST_PP_DEC_22
#define BOOST_PP_DEC_023 BOOST_PP_DEC_23
#define BOOST_PP_DEC_024 BOOST_PP_DEC_24
#define BOOST_PP_DEC_025 BOOST_PP_DEC_25
#define BOOST_PP_DEC_026 BOOST_PP_DEC_26
#define BOOST_PP_DEC_027 BOOST_PP_DEC_27
#define BOOST_PP_DEC_028 BOOST_PP_DEC_28
#define BOOST_PP_DEC_029 BOOST_PP_DEC_29
#define BOOST_PP_DEC_030 BOOST_PP_DEC_30
#define BOOST_PP_DEC_031 BOOST_PP_DEC_31
#define BOOST_PP_DEC_032 BOOST_PP_DEC_32
#define BOOST_PP_DEC_033 BOOST_PP_DEC_33
#define BOOST_PP_DEC_034 BOOST_PP_DEC_34
#define BOOST_PP_DEC_035 BOOST_PP_DEC_35
#define BOOST_PP_DEC_036 BOOST_PP_DEC_36
#define BOOST_PP_DEC_037 BOOST_PP_DEC_37
#define BOOST_PP_DEC_038 BOOST_PP_DEC_38
#define BOOST_PP_DEC_039 BOOST_PP_DEC_39
#define BOOST_PP_DEC_040 BOOST_PP_DEC_40
#define BOOST_PP_DEC_041 BOOST_PP_DEC_41
#define BOOST_PP_DEC_042 BOOST_PP_DEC_42
#define BOOST_PP_DEC_043 BOOST_PP_DEC_43
#define BOOST_PP_DEC_044 BOOST_PP_DEC_44
#define BOOST_PP_DEC_045 BOOST_PP_DEC_45
#define BOOST_PP_DEC_046 BOOST_PP_DEC_46
#define BOOST_PP_DEC_047 BOOST_PP_DEC_47
#define BOOST_PP_DEC_048 BOOST_PP_DEC_48
#define BOOST_PP_DEC_049 BOOST_PP_DEC_49
#define BOOST_PP_DEC_050 BOOST_PP_DEC_50
#define BOOST_PP_DEC_051 BOOST_PP_DEC_51
#define BOOST_PP_DEC_052 BOOST_PP_DEC_52
#define BOOST_PP_DEC_053 BOOST_PP_DEC_53
#define BOOST_PP_DEC_054 BOOST_PP_DEC_54
#define BOOST_PP_DEC_055 BOOST_PP_DEC_55
#define BOOST_PP_DEC_056 BOOST_PP_DEC_56
#define BOOST_PP_DEC_057 BOOST_PP_DEC_57
#define BOOST_PP_DEC_058 BOOST_PP_DEC_58
#define BOOST_PP_DEC_059 BOOST_PP_DEC_59
#define BOOST_PP_DEC_060 BOOST_PP_DEC_60
#define BOOST_PP_DEC_061 BOOST_PP_DEC_61
#define BOOST_PP_DEC_062 BOOST_PP_DEC_62
#define BOOST_PP_DEC_063 BOOST_PP_DEC_63

#define XXX_TO_NUMx(x) 0 ## x
#define XXX_TO_NUM(x) BOOST_PP_ADD(0,XXX_TO_NUMx(x))
#define XXX_STRINGIZEX(x) # x
#define XXX_VSTRINGIZE_SINGLE(a,b,x) XXX_STRINGIZE(x)
#define XXX_VSTRINGIZE_TUPLE(tpl) XXX_TUPLE_FOR_EACH(XXX_VSTRINGIZE_SINGLE,,tpl)
#define XXX_TUPLE_SIZE(TUPLE) XXX_TO_NUM(XXX_TUPLE_CHOICE(XXX_TUPLE_SIZE_INTERNAL(TUPLE)))
#define XXX_TUPLE_FOR_EACH(MACRO,DATA,TUPLE) BOOST_PP_LIST_FOR_EACH(MACRO,DATA,BOOST_PP_TUPLE_TO_LIST(XXX_TUPLE_SIZE(TUPLE),TUPLE))
#define XXX_STRINGIZE(x) XXX_STRINGIZEX(x)
#define XXX_VSTRINGIZE(...) XXX_VSTRINGIZE_TUPLE((__VA_ARGS__))
#define XXX_CAST_TO_VOID_ELEMENT(r,data,elem) (void)(elem);
#define XXX_CAST_TO_VOID_INTERNAL(TUPLE) XXX_TUPLE_FOR_EACH(XXX_CAST_TO_VOID_ELEMENT,,TUPLE)    
#define XXX_CAST_TO_VOID(...) XXX_CAST_TO_VOID_INTERNAL((__VA_ARGS__))
#define XXX_ENUM_EXTRACT_SP(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en) = BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),1,en)
#define XXX_ENUM_ELEMENT(r,data,elem) BOOST_PP_IF( XXX_TUPLE_SIZE(elem), XXX_ENUM_EXTRACT_SP(elem), elem) ,
#define XXX_ENUM_EXTRACT_ELEMENT(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en)
#define XXX_ENUM_CASE_ELEMENT(en) BOOST_PP_IF( XXX_TUPLE_SIZE(en), XXX_ENUM_EXTRACT_ELEMENT(en), en )
#define XXX_ENUM_CASE(r,data,elem) case data :: XXX_ENUM_CASE_ELEMENT(elem) : return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem));
#define XXX_ENUM_IFELSE(r,data,elem) else if( en == data :: XXX_ENUM_CASE_ELEMENT(elem)) { return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)); }
#define XXX_ENUM_CASTLIST(r,data,elem) { XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },
#define XXX_ENUM_QUALIFIED_CASTLIST(r,data,elem) { #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },

#define XXX_ENUM_INTERNAL(TYPE,NAME,TUPLE)                       \
enum TYPE                                                        \
{                                                                \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_ELEMENT,,TUPLE)                   \
   BOOST_PP_CAT(last_enum_,NAME)                                 \
};                                                               \
                                                                 \
inline                                                           \
const char* to_string( NAME en )                                 \
{                                                                \
   if(false)                                                     \
   {                                                             \
   }                                                             \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_IFELSE,NAME,TUPLE)                \
   else if( en == NAME :: BOOST_PP_CAT(last_enum_,NAME) )        \
   {                                                             \
     return XXX_VSTRINGIZE(NAME,::,BOOST_PP_CAT(last_enum_,NAME));  \
   }                                                             \
   else                                                          \
   {                                                             \
     return "Invalid enum value specified for " # NAME;          \
   }                                                             \
}                                                                \
                                                                 \
inline                                                           \
std::ostream& operator<<( std::ostream& os, const NAME& en )     \
{                                                                \
   os << to_string(en);                                          \
   return os;                                                    \
}                                                                \
                                                                 \
inline                                                           \
NAME do_enum_cast( const std::string& s, const ::xxx::enum_cast_adl_helper<NAME>& ) \
{                                                                \
  static const std::unordered_map<std::string,NAME> map =        \
  {                                                              \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_CASTLIST,NAME,TUPLE)             \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_QUALIFIED_CASTLIST,NAME,TUPLE)   \
  };                                                             \
                                                                 \
  auto cit = map.find(s);                                        \
  if( cit == map.end() )                                         \
  {                                                              \
    throw std::runtime_error("Invalid value to cast to enum");   \
  }                                                              \
  return cit->second;                                            \
}

#define XXX_ENUM(NAME,TUPLE) XXX_ENUM_INTERNAL(NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS(NAME,TUPLE) XXX_ENUM_INTERNAL(class NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(class NAME : TYPE,NAME,TUPLE)
#define XXX_ENUM_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(NAME : TYPE,NAME,TUPLE)

Uso

#include "xxx_enum.h"  // the above lib
#include <iostream>

XXX_ENUM(foo,(a,b,(c,42)));

int main()
{
  std::cout << "foo::a = "            << foo::a            <<'\n';
  std::cout << "(int)foo::c = "       << (int)foo::c       <<'\n';
  std::cout << "to_string(foo::b) = " << to_string(foo::b) <<'\n';
  std::cout << "xxx::enum_cast<foo>(\"b\") = " << xxx::enum_cast<foo>("b") <<'\n';
}

Compilación (copiar pegar encabezado dentro main.cpp)

> g++ --version | sed 1q
g++ (GCC) 4.9.2

> g++ -std=c++14 -pedantic -Wall -Wextra main.cpp
main.cpp:268:31: warning: extra ';' [-Wpedantic]
     XXX_ENUM(foo,(a,b,(c,42)));
                               ^

Salida

foo::a = foo::a
(int)foo::c = 42
to_string(foo::b) = foo::b
xxx::enum_cast<foo>("b") = foo::b

55
Este bloque de código es un viaje loco a través de los increíbles paisajes de la magia negra de metaprogramación. De hecho, me sentí aliviado al llegar main- ¡Hogar, dulce hogar!
Quentin

Acabo de agregar un enlace a coliru para verificar la salida (hay algunas advertencias, haga clic en el enlace dentro de su respuesta). También me he dividido en Lib / Usage. ¿Las cosas namespace xxxse pueden mover al lugar del encabezado? Puede decir en la introducción su uso boost/preprocessor.hppy, por lo tanto, la respuesta es moderna y cumple con C ++ . Corrija las advertencias y limpie un poco el código fuente para obtener una mejor calidad.
olibre

@olibre: Creo que es copypastad de 5 encabezados diferentes en nuestra biblioteca. El enum_cast es de otra parte más general, pero pensé agregarlo también para ver para qué sirve el do_enum_cast en la macro. Las advertencias son solo de mi main<tab>de vim, incluidos los args que no uso. No creo que este código se pueda limpiar realmente, es solo para mostrar lo que se puede hacer y no se debe hacer;) y si lo cambio aquí ya no es el código que uso en producción ... es una de esas cosas frágiles que una vez que funciona, es mejor que nunca lo toque, ya que podría colapsar de una manera que nadie podría predecir.
PlasmaHH

Muy bien Plasma, veo que esto puede verse como una Prueba de Concepto . Pero hay demasiados gastos generales macro para ser votados. Sin embargo, gracias por compartir. Saludos
Olibre

Hola plasma He realizado una limpieza profunda de código fuente + completada por compilación y ejecución de salida. Por favor revisa mi edición . Espero que esto esté bien para ti. ¿La respuesta es más valiosa? Sin embargo, la macro sobrecarga sigue siendo horrible! Que tengas un buen día :-) Saludos
olibre

2

La siguiente solución se basa en un std::array<std::string,N>para una enumeración dada.

Para enumque std::stringla conversión podemos simplemente echar la enumeración de size_ty buscar la cadena de la matriz. La operación es O (1) y no requiere asignación de montón.

#include <boost/preprocessor/seq/transform.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/stringize.hpp>

#include <string>
#include <array>
#include <iostream>

#define STRINGIZE(s, data, elem) BOOST_PP_STRINGIZE(elem)

// ENUM
// ============================================================================
#define ENUM(X, SEQ) \
struct X {   \
    enum Enum {BOOST_PP_SEQ_ENUM(SEQ)}; \
    static const std::array<std::string,BOOST_PP_SEQ_SIZE(SEQ)> array_of_strings() { \
        return {{BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(STRINGIZE, 0, SEQ))}}; \
    } \
    static std::string to_string(Enum e) { \
        auto a = array_of_strings(); \
        return a[static_cast<size_t>(e)]; \
    } \
}

Para std::stringque enumla conversión tendríamos que hacer una búsqueda lineal en la matriz y emitir el índice de matriz a enum.

Pruébelo aquí con ejemplos de uso: http://coliru.stacked-crooked.com/a/e4212f93bee65076

Editar: Se modificó mi solución para que el Enum personalizado se pueda usar dentro de una clase.


Gracias por tu interesante respuesta. Vuelva a trabajar su propuesta para usar su macro dentro de una clase. Ver coliru.stacked-crooked.com/a/00d362eba836d04b Por otra parte trate de usar constexpry noexeptpalabras clave cuando sea posible. Saludos :-)
olibre

La pregunta no especificaba este requisito.
FKaria

Pregunta actualizada (ver ejemplo). Otros dos requisitos: (1) tipo de soporte de enumeración y (2) los valores pueden ser diferentes de la secuencia 0, 1, 2 ...
olibre

Reformé mi solución para que pueda usarse dentro de una clase. Sin embargo, no he descubierto cómo hacer que los valores sean diferentes de 0,1,2.
FKaria

Hola FKaria Muchas gracias por tu retrabajo. Hice algunos cambios para admitir varias enumeraciones dentro de la misma clase, y también para admitir el enum class X : Typeformato. Por favor revise mi contribución: coliru.stacked-crooked.com/a/b02db9190d3491a3 ¿Qué piensa sobre mis cambios? ¿Tienes alguna idea para apoyar los valores establecidos en la enumeración? Ejemplo enum E{A=3, B=6, C=A-B};Cheers
olibre

2

Esta esencia proporciona un mapeo simple basado en plantillas variadas de C ++.

Esta es una versión simplificada en C ++ 17 del mapa basado en tipos desde la esencia :

#include <cstring> // http://stackoverflow.com/q/24520781

template<typename KeyValue, typename ... RestOfKeyValues>
struct map {
  static constexpr typename KeyValue::key_t get(const char* val) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)  // C++17 if constexpr
      return KeyValue::key; // Returns last element
    else {
      static_assert(KeyValue::val != nullptr,
                  "Only last element may have null name");
      return strcmp(val, KeyValue::val()) 
            ? map<RestOfKeyValues...>::get(val) : KeyValue::key;
    }
  }
  static constexpr const char* get(typename KeyValue::key_t key) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)
      return (KeyValue::val != nullptr) && (key == KeyValue::key)
            ? KeyValue::val() : "";
    else
      return (key == KeyValue::key) 
            ? KeyValue::val() : map<RestOfKeyValues...>::get(key);
  }
};

template<typename Enum, typename ... KeyValues>
class names {
  typedef map<KeyValues...> Map;
public:
  static constexpr Enum get(const char* nam) noexcept {
    return Map::get(nam);
  }
  static constexpr const char* get(Enum key) noexcept {
    return Map::get(key);
  }
};

Un ejemplo de uso:

enum class fasion {
    fancy,
    classic,
    sporty,
    emo,
    __last__ = emo,
    __unknown__ = -1
};

#define NAME(s) static inline constexpr const char* s() noexcept {return #s;}
namespace name {
    NAME(fancy)
    NAME(classic)
    NAME(sporty)
    NAME(emo)
}

template<auto K, const char* (*V)()>  // C++17 template<auto>
struct _ {
    typedef decltype(K) key_t;
    typedef decltype(V) name_t;
    static constexpr key_t  key = K; // enum id value
    static constexpr name_t val = V; // enum id name
};

typedef names<fasion,
    _<fasion::fancy, name::fancy>,
    _<fasion::classic, name::classic>,
    _<fasion::sporty, name::sporty>,
    _<fasion::emo, name::emo>,
    _<fasion::__unknown__, nullptr>
> fasion_names;

Se map<KeyValues...>puede usar en ambas direcciones:

  • fasion_names::get(fasion::emo)
  • fasion_names::get("emo")

Este ejemplo está disponible en godbolt.org

int main ()
{
  constexpr auto str = fasion_names::get(fasion::emo);
  constexpr auto fsn = fasion_names::get(str);
  return (int) fsn;
}

El resultado de gcc-7 -std=c++1z -Ofast -S

main:
        mov     eax, 3
        ret

1
Muy interesante forma de metaprogramación. He intentado simplificar un poco la respuesta para que sea autónoma (sin depender del enlace Gist). Para ser conciso y comprensible, finalmente he editado mucho su respuesta. ¿Sigues de acuerdo con mis cambios? Saludos ;-)
olibre

2

Este problema también me ha frustrado durante mucho tiempo, junto con el problema de convertir un tipo a cadena de forma adecuada. Sin embargo, para el último problema, me sorprendió la solución explicada en ¿Es posible imprimir el tipo de una variable en C ++ estándar? , usando la idea de ¿Puedo obtener nombres de tipo C ++ de una manera constexpr? . Usando esta técnica, se puede construir una función análoga para obtener un valor de enumeración como cadena:

#include <iostream>
using namespace std;

class static_string
{
    const char* const p_;
    const std::size_t sz_;

public:
    typedef const char* const_iterator;

    template <std::size_t N>
    constexpr static_string(const char(&a)[N]) noexcept
        : p_(a)
        , sz_(N - 1)
    {}

    constexpr static_string(const char* p, std::size_t N) noexcept
        : p_(p)
        , sz_(N)
    {}

    constexpr const char* data() const noexcept { return p_; }
    constexpr std::size_t size() const noexcept { return sz_; }

    constexpr const_iterator begin() const noexcept { return p_; }
    constexpr const_iterator end()   const noexcept { return p_ + sz_; }

    constexpr char operator[](std::size_t n) const
    {
        return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
    }
};

inline std::ostream& operator<<(std::ostream& os, static_string const& s)
{
    return os.write(s.data(), s.size());
}

/// \brief Get the name of a type
template <class T>
static_string typeName()
{
#ifdef __clang__
    static_string p = __PRETTY_FUNCTION__;
    return static_string(p.data() + 30, p.size() - 30 - 1);
#elif defined(_MSC_VER)
    static_string p = __FUNCSIG__;
    return static_string(p.data() + 37, p.size() - 37 - 7);
#endif

}

namespace details
{
    template <class Enum>
    struct EnumWrapper
    {
        template < Enum enu >
        static static_string name()
        {
#ifdef __clang__
            static_string p = __PRETTY_FUNCTION__;
            static_string enumType = typeName<Enum>();
            return static_string(p.data() + 73 + enumType.size(), p.size() - 73 - enumType.size() - 1);
#elif defined(_MSC_VER)
            static_string p = __FUNCSIG__;
            static_string enumType = typeName<Enum>();
            return static_string(p.data() + 57 + enumType.size(), p.size() - 57 - enumType.size() - 7);
#endif
        }
    };
}

/// \brief Get the name of an enum value
template <typename Enum, Enum enu>
static_string enumName()
{
    return details::EnumWrapper<Enum>::template name<enu>();
}

enum class Color
{
    Blue = 0,
    Yellow = 1
};


int main() 
{
    std::cout << "_" << typeName<Color>() << "_"  << std::endl;
    std::cout << "_" << enumName<Color, Color::Blue>() << "_"  << std::endl;
    return 0;
}

El código anterior solo se ha probado en Clang (consulte https://ideone.com/je5Quv ) y VS2015, pero debe ser adaptable a otros compiladores jugando un poco con las constantes enteras. Por supuesto, todavía usa macros debajo del capó, pero al menos uno no necesita acceso a la implementación de enumeración.


Esto falla con g ++ 6.3.0 y C ++ 14.
einpoklum

Interesante porque la enumeración se puede declarar normalmente sin tener que envolverla en una macro. Aunque no me gustan las dependencias del compilador y las constantes mágicas.
zett42

2

Tomé la idea de @antron y la implementé de manera diferente: generando una verdadera clase de enumeración .

Esta implementación cumple con todos los requisitos enumerados en la pregunta original pero actualmente solo tiene una limitación real : supone que los valores de enumeración no se proporcionan o, si se proporcionan, deben comenzar con 0 y subir secuencialmente sin espacios.

Esto no es una limitación intrínseca, simplemente que no uso valores de enumeración ad-hoc. Si esto es necesario, se puede reemplazar la búsqueda de vectores con la implementación tradicional de cambio / caso.

La solución utiliza algunos c ++ 17 para las variables en línea, pero esto se puede evitar fácilmente si es necesario. También usa boost: trim por simplicidad.

Lo más importante, solo requiere 30 líneas de código y no macros de magia negra. El código está abajo. Está destinado a colocarse en el encabezado e incluirse en múltiples módulos de compilación.

Se puede usar de la misma manera que se sugirió anteriormente en este hilo:

ENUM(Channel, int, Red, Green = 1, Blue)
std::out << "My name is " << Channel::Green;
//prints My name is Green

Por favor, avíseme si esto es útil y cómo se puede mejorar aún más.


#include <boost/algorithm/string.hpp>   
struct EnumSupportBase {
  static std::vector<std::string> split(const std::string s, char delim) {
    std::stringstream ss(s);
    std::string item;
    std::vector<std::string> tokens;
    while (std::getline(ss, item, delim)) {
        auto pos = item.find_first_of ('=');
        if (pos != std::string::npos)
            item.erase (pos);
        boost::trim (item);
        tokens.push_back(item);
    }
    return tokens;
  }
};
#define ENUM(EnumName, Underlying, ...) \
    enum class EnumName : Underlying { __VA_ARGS__, _count }; \
    struct EnumName ## Support : EnumSupportBase { \
        static inline std::vector<std::string> _token_names = split(#__VA_ARGS__, ','); \
        static constexpr const char* get_name(EnumName enum_value) { \
            int index = (int)enum_value; \
            if (index >= (int)EnumName::_count || index < 0) \
               return "???"; \
            else \
               return _token_names[index].c_str(); \
        } \
    }; \
    inline std::ostream& operator<<(std::ostream& os, const EnumName & es) { \
        return os << EnumName##Support::get_name(es); \
    } 

2

Siempre que esté de acuerdo con escribir un .h/.cpppar separado para cada enumeración consultable, esta solución funciona con casi la misma sintaxis y capacidades que una enumeración c ++ normal:

// MyEnum.h
#include <EnumTraits.h>
#ifndef ENUM_INCLUDE_MULTI
#pragma once
#end if

enum MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = AAA + BBB
};

El .cpparchivo es de 3 líneas de repetitivo:

// MyEnum.cpp
#define ENUM_DEFINE MyEnum
#define ENUM_INCLUDE <MyEnum.h>
#include <EnumTraits.inl>

Ejemplo de uso:

for (MyEnum value : EnumTraits<MyEnum>::GetValues())
    std::cout << EnumTraits<MyEnum>::GetName(value) << std::endl;

Código

Esta solución requiere 2 archivos fuente:

// EnumTraits.h
#pragma once
#include <string>
#include <unordered_map>
#include <vector>

#define ETRAITS
#define EDECL(x) x

template <class ENUM>
class EnumTraits
{
public:
    static const std::vector<ENUM>& GetValues()
    {
        return values;
    }

    static ENUM GetValue(const char* name)
    {
        auto match = valueMap.find(name);
        return (match == valueMap.end() ? ENUM() : match->second);
    }

    static const char* GetName(ENUM value)
    {
        auto match = nameMap.find(value);
        return (match == nameMap.end() ? nullptr : match->second);
    }

public:
    EnumTraits() = delete;

    using vector_type = std::vector<ENUM>;
    using name_map_type = std::unordered_map<ENUM, const char*>;
    using value_map_type = std::unordered_map<std::string, ENUM>;

private:
    static const vector_type values;
    static const name_map_type nameMap;
    static const value_map_type valueMap;
};

struct EnumInitGuard{ constexpr const EnumInitGuard& operator=(int) const { return *this; } };
template <class T> constexpr T& operator<<=(T&& x, const EnumInitGuard&) { return x; }

...y

// EnumTraits.inl
#define ENUM_INCLUDE_MULTI

#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

using EnumType = ENUM_DEFINE;
using TraitsType = EnumTraits<EnumType>;
using VectorType = typename TraitsType::vector_type;
using NameMapType = typename TraitsType::name_map_type;
using ValueMapType = typename TraitsType::value_map_type;
using NamePairType = typename NameMapType::value_type;
using ValuePairType = typename ValueMapType::value_type;

#define ETRAITS ; const VectorType TraitsType::values
#define EDECL(x) EnumType::x <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const NameMapType TraitsType::nameMap
#define EDECL(x) NamePairType(EnumType::x, #x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const ValueMapType TraitsType::valueMap
#define EDECL(x) ValuePairType(#x, EnumType::x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

Explicación

Esta implementación aprovecha el hecho de que la lista arriostrada de elementos de una definición de enumeración también se puede usar como una lista de inicializadores arriostrados para la inicialización de miembros de clase.

Cuando ETRAITSse evalúa en el contexto de EnumTraits.inl, se expande a una definición de miembro estático para elEnumTraits<> clase.

La EDECLmacro transforma cada miembro de enumeración en valores de lista de inicializador que posteriormente se pasan al constructor de miembros para completar la información de enumeración.

La EnumInitGuardclase está diseñada para consumir los valores de inicializador de enumeración y luego colapsar, dejando una lista pura de datos de enumeración.

Beneficios

  • c++sintaxis
  • Funciona de manera idéntica para ambos enumy enum class(* casi)
  • Funciona para enumtipos con cualquier tipo subyacente numérico
  • Funciona para enumtipos con valores de inicializador automático, explícito y fragmentado
  • Funciona para renombrar en masa (enlace intellisense preservado)
  • Solo 5 símbolos de preprocesador (3 globales)

* En contraste con enums, los inicializadores enenum class tipos que hacen referencia a otros valores de la misma enumeración deben tener esos valores totalmente calificados

Desventajas

  • Requiere un .h/.cpppar separado para cada consultableenum
  • Depende de enrevesado macroy includemágico
  • Los errores menores de sintaxis explotan en errores mucho más grandes
  • La definición classo namespaceenumeración de ámbitos no es trivial
  • Sin inicialización en tiempo de compilación

Comentarios

Intellisense se quejará un poco sobre el acceso de miembros privados al abrir EnumTraits.inl, pero dado que las macros expandidas en realidad están definiendo miembros de clase, eso no es realmente un problema.

El #ifndef ENUM_INCLUDE_MULTIbloque en la parte superior del archivo de encabezado es una molestia menor que probablemente podría reducirse a una macro o algo así, pero es lo suficientemente pequeño como para vivir con su tamaño actual.

La declaración de una enumeración de ámbito de espacio de nombres requiere que la enumeración se declare primero dentro de su ámbito de espacio de nombres y luego se defina en el espacio de nombres global. Además, cualquier inicializador de enumeración que use valores de la misma enumeración debe tener esos valores totalmente calificados.

namespace ns { enum MyEnum : int; }
enum ns::MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = ns::MyEnum::AAA + ns::MyEnum::BBB
}

2

No estoy seguro de si este enfoque ya está cubierto en una de las otras respuestas (en realidad lo está, ver más abajo). Encontré el problema muchas veces y no encontré una solución que no utilizara macros ofuscadas o bibliotecas de terceros. Por lo tanto, decidí escribir mi propia versión macro ofuscada.

Lo que quiero habilitar es el equivalente de

enum class test1 { ONE, TWO = 13, SIX };

std::string toString(const test1& e) { ... }

int main() {
    test1 x;
    std::cout << toString(x) << "\n";
    std::cout << toString(test1::TWO) << "\n";
    std::cout << static_cast<std::underlying_type<test1>::type>(test1::TWO) << "\n";
    //std::cout << toString(123);// invalid
}

que debería imprimir

ONE
TWO
13

No soy fanático de las macros. Sin embargo, a menos que c ++ admita de forma nativa la conversión de enumeraciones a cadenas, uno debe usar algún tipo de generación de código y / o macros (y dudo que esto suceda demasiado pronto). Estoy usando una macro X :

// x_enum.h
#include <string>
#include <map>
#include <type_traits>
#define x_begin enum class x_name {
#define x_val(X) X
#define x_value(X,Y) X = Y
#define x_end };
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end

#define x_begin inline std::string toString(const x_name& e) { \
                static std::map<x_name,std::string> names = { 
#define x_val(X)      { x_name::X , #X }
#define x_value(X,Y)  { x_name::X , #X }
#define x_end }; return names[e]; }
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end
#undef x_name
#undef x_enum_def

La mayor parte es la definición y la definición de símbolos que el usuario pasará como parámetro al X-marco a través de una inclusión. El uso es asi

#define x_name test1
#define x_enum_def x_begin x_val(ONE) , \
                           x_value(TWO,13) , \
                           x_val(SIX) \
                   x_end
#include "x_enum.h"

Demo en vivo

Tenga en cuenta que todavía no incluí elegir el tipo subyacente. No lo necesitaba hasta ahora, pero debería ser sencillo modificar el código para habilitarlo.

Solo después de escribir esto me di cuenta de que es bastante similar a la respuesta de eferions . Tal vez lo leí antes y tal vez fue la principal fuente de inspiración. Siempre fallaba en comprender las macros X hasta que escribí la mía;).


1

Soluciones que usan enum dentro de class / struct (por defecto de struct con miembros públicos) y operadores sobrecargados:

struct Color
{
    enum Enum { RED, GREEN, BLUE };
    Enum e;

    Color() {}
    Color(Enum e) : e(e) {}

    Color operator=(Enum o) { e = o; return *this; }
    Color operator=(Color o) { e = o.e; return *this; }
    bool operator==(Enum o) { return e == o; }
    bool operator==(Color o) { return e == o.e; }
    operator Enum() const { return e; }

    std::string toString() const
    {
        switch (e)
        {
        case Color::RED:
            return "red";
        case Color::GREEN:
            return "green";
        case Color::BLUE:
            return "blue";
        default:
            return "unknown";
        }
    }
};

Desde el exterior se ve casi exactamente como una enumeración de clase:

Color red;
red = Color::RED;
Color blue = Color::BLUE;

cout << red.toString() << " " << Color::GREEN << " " << blue << endl;

Esto generará "rojo 1 2". Posiblemente podría sobrecargar << para hacer que la salida azul sea una cadena (aunque podría causar ambigüedad, por lo que no es posible), pero no funcionaría con Color :: GREEN ya que no se convierte automáticamente en Color.

El propósito de tener una conversión implícita a Enum (que se convierte implícitamente a int o tipo dado) es poder hacer:

Color color;
switch (color) ...

Esto funciona, pero también significa que esto también funciona:

int i = color;

Con una clase enum no se compilaría. Debe tener cuidado si sobrecarga dos funciones tomando la enumeración y un entero, o elimina la conversión implícita ...

Otra solución implicaría usar una clase enum real y miembros estáticos:

struct Color
{
    enum class Enum { RED, GREEN, BLUE };
    static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;

    //same as previous...
};

Posiblemente toma más espacio y es más largo de hacer, pero causa un error de compilación para las conversiones int implícitas. ¡Usaría este por eso!

Sin embargo, seguramente hay gastos generales con esto, pero creo que es más simple y se ve mejor que otro código que he visto. También existe la posibilidad de agregar funcionalidad, que podría estar dentro del alcance de la clase.

Editar : esto funciona y la mayoría se puede compilar antes de la ejecución:

class Color
{
public:
    enum class Enum { RED, GREEN, BLUE };
    static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;

    constexpr Color() : e(Enum::RED) {}
    constexpr Color(Enum e) : e(e) {}

    constexpr bool operator==(Enum o) const { return e == o; }
    constexpr bool operator==(Color o) const { return e == o.e; }
    constexpr operator Enum() const { return e; }

    Color& operator=(Enum o) { const_cast<Enum>(this->e) = o; return *this; }
    Color& operator=(Color o) { const_cast<Enum>(this->e) = o.e; return *this; }

    std::string toString() const
    {
        switch (e)
        {
        case Enum::RED:
            return "red";
        case Enum::GREEN:
            return "green";
        case Enum::BLUE:
            return "blue";
        default:
            return "unknown";
        }
    }
private:
    const Enum e;
};

Esto es muy interesante :-) Sin embargo, su versión actual implica que tiene que escribir manualmente las cosas case Enum::RED: return "red";. La pregunta es sobre automatizar estas cosas por el compilador (en el momento de la compilación). La idea de la pregunta es solo cambiar o agregar valores de enumeración sin tener que actualizar las cosas toString(). ¿Lo ves? Gracias
olibre

1

Solución muy simple con una gran restricción: no puede asignar valores personalizados a los enumvalores, pero con la expresión regular correcta, sí. También puede agregar un mapa para traducirlos nuevamente a enumvalores sin mucho más esfuerzo:

#include <vector>
#include <string>
#include <regex>
#include <iterator>

std::vector<std::string> split(const std::string& s, 
                               const std::regex& delim = std::regex(",\\s*"))
{
    using namespace std;
    vector<string> cont;
    copy(regex_token_iterator<string::const_iterator>(s.begin(), s.end(), delim, -1), 
         regex_token_iterator<string::const_iterator>(),
         back_inserter(cont));
    return cont;
}

#define EnumType(Type, ...)     enum class Type { __VA_ARGS__ }

#define EnumStrings(Type, ...)  static const std::vector<std::string> \
                                Type##Strings = split(#__VA_ARGS__);

#define EnumToString(Type, ...) EnumType(Type, __VA_ARGS__); \
                                EnumStrings(Type, __VA_ARGS__)

Ejemplo de uso:

EnumToString(MyEnum, Red, Green, Blue);

Gracias Malem por tu idea innovadora. He editado tu respuesta para mejorar la legibilidad. Espero que les gusten mis cambios. Continúe mejorando su respuesta: (1) amplíe la sección "Ejemplo de uso" con algo como auto name = MyEnumStrings["Red"];: (2) ¿Por qué lo usa enum class? - (3) ¿Apoya enum class MyEnum : char { Red, Green, Blue };? - (4) Explicar la función split()- (5) ¿Necesita un parámetro const std::regex& delim? - (6) ¿Qué pasa con la generación MyEnumStringsen tiempo de compilación? => ¿Puedes usar constexpr? ... Saludos :-)
olibre

Realmente me gusta este enfoque. Realmente corto y fácil de entender.
Anton Holmberg

1

EDITAR: consulte a continuación para obtener una versión más nueva

Como se mencionó anteriormente, N4113 es la solución final a este asunto, pero tendremos que esperar más de un año para verlo salir.

Mientras tanto, si desea dicha función, deberá recurrir a plantillas "simples" y algo de magia de preprocesador.

Enumerador

template<typename T>
class Enum final
{
    const char* m_name;
    const T m_value;
    static T m_counter;

public:
    Enum(const char* str, T init = m_counter) : m_name(str), m_value(init) {m_counter = (init + 1);}

    const T value() const {return m_value;}
    const char* name() const {return m_name;}
};

template<typename T>
T Enum<T>::m_counter = 0;

#define ENUM_TYPE(x)      using Enum = Enum<x>;
#define ENUM_DECL(x,...)  x(#x,##__VA_ARGS__)
#define ENUM(...)         const Enum ENUM_DECL(__VA_ARGS__);

Uso

#include <iostream>

//the initialization order should be correct in all scenarios
namespace Level
{
    ENUM_TYPE(std::uint8)
    ENUM(OFF)
    ENUM(SEVERE)
    ENUM(WARNING)
    ENUM(INFO, 10)
    ENUM(DEBUG)
    ENUM(ALL)
}

namespace Example
{
    ENUM_TYPE(long)
    ENUM(A)
    ENUM(B)
    ENUM(C, 20)
    ENUM(D)
    ENUM(E)
    ENUM(F)
}

int main(int argc, char** argv)
{
    Level::Enum lvl = Level::WARNING;
    Example::Enum ex = Example::C;
    std::cout << lvl.value() << std::endl; //2
    std::cout << ex.value() << std::endl; //20
}

Explicación simple

Enum<T>::m_counterse establece en 0 dentro de cada declaración de espacio de nombres.
( ¿Podría alguien indicarme dónde se menciona ^^ este comportamiento ^^ en el estándar? )
La magia del preprocesador automatiza la declaración de enumeradores.

Desventajas

  • No es un enumtipo verdadero , por lo tanto, no se puede promocionar a int
  • No se puede usar en cajas de interruptores

Solución alternativa

Este sacrifica la numeración de líneas (no realmente) pero puede usarse en casos de cambio .

#define ENUM_TYPE(x) using type = Enum<x>
#define ENUM(x)      constexpr type x{__LINE__,#x}

template<typename T>
struct Enum final
{
    const T value;
    const char* name;

    constexpr operator const T() const noexcept {return value;}
    constexpr const char* operator&() const noexcept {return name;}
};

Errata

#line 0entra -pedanticen conflicto con GCC y clang.

Solución alterna

Comience en #line 1y reste 1 de __LINE__.
O no lo uses -pedantic.
Y mientras lo hacemos, evite VC ++ a toda costa, siempre ha sido una broma de un compilador.

Uso

#include <iostream>

namespace Level
{
    ENUM_TYPE(short);
    #line 0
    ENUM(OFF);
    ENUM(SEVERE);
    ENUM(WARNING);
    #line 10
    ENUM(INFO);
    ENUM(DEBUG);
    ENUM(ALL);
    #line <next line number> //restore the line numbering
};

int main(int argc, char** argv)
{
    std::cout << Level::OFF << std::endl;   // 0
    std::cout << &Level::OFF << std::endl;  // OFF

    std::cout << Level::INFO << std::endl;  // 10
    std::cout << &Level::INFO << std::endl; // INFO

    switch(/* any integer or integer-convertible type */)
    {
    case Level::OFF:
        //...
        break;

    case Level::SEVERE:
        //...
        break;

    //...
    }

    return 0;
}

Implementación y uso en la vida real

r3dVoxel - Enum
r3dVoxel - ELoggingLevel

Referencia rápida

#line lineno - cppreference.com


0

Escribí una biblioteca para resolver este problema, todo sucede en tiempo de compilación, excepto para recibir el mensaje.

Uso:

Use macro DEF_MSGpara definir un par de macro y mensaje:

DEF_MSG(CODE_OK,   "OK!")
DEF_MSG(CODE_FAIL, "Fail!")

CODE_OKes la macro a usar y "OK!"es el mensaje correspondiente.

Use get_message()o simplemente gm()para recibir el mensaje:

get_message(CODE_FAIL);  // will return "Fail!"
gm(CODE_FAIL);           // works exactly the same as above

Use MSG_NUMpara averiguar cuántas macros se han definido. Esto aumentará automáticamente, no necesita hacer nada.

Mensajes predefinidos:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

Proyecto: libcodemsg


La biblioteca no crea datos adicionales. Todo sucede en tiempo de compilación. En message_def.h, genera un enumllamado MSG_CODE; adentro message_def.c, genera una variable que contiene todas las cadenas static const char* _g_messages[].

En tal caso, la biblioteca se limita a crear enumsolo uno . Esto es ideal para valores de retorno, por ejemplo:

MSG_CODE foo(void) {
    return MSG_OK; // or something else
}

MSG_CODE ret = foo();

if (MSG_OK != ret) {
    printf("%s\n", gm(ret););
}

Otra cosa que me gusta de este diseño es que puedes administrar definiciones de mensajes en diferentes archivos.


Encontré que la solución a esta pregunta se ve mucho mejor.


Hola madwyn Gracias por tu idea ¿Pero cómo funciona? ¿Qué es la sobrecarga? (cero sobrecarga o crea datos adicionales?). Su propuesta parece estar bien, pero desafortunadamente, DEF_MSGse debe usar / actualizar / mantener una declaración para cada enumvalor: - / Y esto es lo que idealmente nos gustaría dejar de hacer ... Saludos
olibre

Gracias por la respuesta, @olibre. Por favor verifique la respuesta actualizada. No veo sobrecarga aquí, excepto que se necesita una llamada de función para acceder a las cadenas. DEF_MSGhace el enumemparejamiento cercano con el mensaje, aunque tiene algunas limitaciones.
Madwyn

Gracias por la explicación adjunta en su respuesta :-) Su lib está bien pero no se puede usar para múltiples enumeraciones: - / ¿Qué pasa con el soporte de enum class(C ++ 11) ? Puede usar constexprpara limitar _g_messagesen tiempo de ejecución. Admite múltiples enumtipos (evitando _g_messages) usando metaprogramación (tipo de transmisión {enum-type, enum-value}) o quizás variables de plantilla (C ++ 14) . Creo que su lib no cumple (¿todavía?) Los requisitos de C ++ 11/14/17. ¿Qué piensas? Saludos ;-)
olibre

1
Gracias por el seguimiento. Aprendí algo nuevo hoy! Las variables de clase y plantilla enum se ven bien. Creo que mi respuesta fue un poco "fuera de tema", ya que tenía sabor a C.
Madwyn

0
#define ENUM_MAKE(TYPE, ...) \
        enum class TYPE {__VA_ARGS__};\
        struct Helper_ ## TYPE { \
            static const String& toName(TYPE type) {\
                int index = static_cast<int>(type);\
                return splitStringVec()[index];}\
            static const TYPE toType(const String& name){\
                static std::unordered_map<String,TYPE> typeNameMap;\
                if( typeNameMap.empty() )\
                {\
                    const StringVector& ssVec = splitStringVec();\
                    for (size_t i = 0; i < ssVec.size(); ++i)\
                        typeNameMap.insert(std::make_pair(ssVec[i], static_cast<TYPE>(i)));\
                }\
                return typeNameMap[name];}\
            static const StringVector& splitStringVec() {\
                static StringVector typeNameVector;\
                if(typeNameVector.empty()) \
                {\
                    typeNameVector = StringUtil::split(#__VA_ARGS__, ",");\
                    for (auto& name : typeNameVector)\
                    {\
                        name.erase(std::remove(name.begin(), name.end(), ' '),name.end()); \
                        name = String(#TYPE) + "::" + name;\
                    }\
                }\
                return typeNameVector;\
            }\
        };


using String = std::string;
using StringVector = std::vector<String>;

   StringVector StringUtil::split( const String& str, const String& delims, unsigned int maxSplits, bool preserveDelims)
    {
        StringVector ret;
        // Pre-allocate some space for performance
        ret.reserve(maxSplits ? maxSplits+1 : 10);    // 10 is guessed capacity for most case

        unsigned int numSplits = 0;

        // Use STL methods 
        size_t start, pos;
        start = 0;
        do 
        {
            pos = str.find_first_of(delims, start);
            if (pos == start)
            {
                // Do nothing
                start = pos + 1;
            }
            else if (pos == String::npos || (maxSplits && numSplits == maxSplits))
            {
                // Copy the rest of the string
                ret.push_back( str.substr(start) );
                break;
            }
            else
            {
                // Copy up to delimiter
                ret.push_back( str.substr(start, pos - start) );

                if(preserveDelims)
                {
                    // Sometimes there could be more than one delimiter in a row.
                    // Loop until we don't find any more delims
                    size_t delimStart = pos, delimPos;
                    delimPos = str.find_first_not_of(delims, delimStart);
                    if (delimPos == String::npos)
                    {
                        // Copy the rest of the string
                        ret.push_back( str.substr(delimStart) );
                    }
                    else
                    {
                        ret.push_back( str.substr(delimStart, delimPos - delimStart) );
                    }
                }

                start = pos + 1;
            }
            // parse up to next real data
            start = str.find_first_not_of(delims, start);
            ++numSplits;

        } while (pos != String::npos);



        return ret;
    }

ejemplo

ENUM_MAKE(MY_TEST, MY_1, MY_2, MY_3)


    MY_TEST s1 = MY_TEST::MY_1;
    MY_TEST s2 = MY_TEST::MY_2;
    MY_TEST s3 = MY_TEST::MY_3;

    String z1 = Helper_MY_TEST::toName(s1);
    String z2 = Helper_MY_TEST::toName(s2);
    String z3 = Helper_MY_TEST::toName(s3);

    MY_TEST q1 = Helper_MY_TEST::toType(z1);
    MY_TEST q2 = Helper_MY_TEST::toType(z2);
    MY_TEST q3 = Helper_MY_TEST::toType(z3);

automáticamente la macro ENUM_MAKE genera 'clase enum' y clase auxiliar con 'función de reflexión enum'.

Para reducir los errores, de una vez Todo se define con un solo ENUM_MAKE.

La ventaja de este código se crea automáticamente para la reflexión y una mirada cercana al código macro, código fácil de entender. El rendimiento de 'enum a string', 'string a enum' es el algoritmo O (1).

Las desventajas son cuando se usa por primera vez, se inicializa la clase auxiliar para el vector de cadena y el mapa de la enum relection. pero si lo desea, también será preinicializado. -


Si bien este código puede responder la pregunta, sería mejor explicar cómo resuelve el problema sin presentar otros y por qué usarlo. Las respuestas de solo código no son útiles a largo plazo.
JAL

Hola chicos, lo siento, no hablo inglés muy bien.
desperado_98

automáticamente la macro ENUM_MAKE genera 'clase enum' y clase auxiliar con 'función de reflexión enum'. / Para reducir errores, de una vez Todo se define con un solo ENUM_MAKE. La ventaja de este código se crea automáticamente para la reflexión y una mirada cercana al código macro, código fácil de entender. El rendimiento 'enum a string', 'string a enum' es el algoritmo O (1). Las desventajas son cuando se usa por primera vez, se inicializa la clase auxiliar para el vector de cadena y el mapa de la enum relection. pero si lo desea, también será preinicializado.
desperado_98

Hola desperado_98. Gracias por tu contribución. Edite su respuesta e inserte en ella el contenido de su comentario. El compilador puede calcular su ejemplo en tiempo de compilación si utiliza algunos trucos de metaprogramación y constexpr. Me refiero a las funciones toName()y toType()puedo evaluarlas durante la compilación y no durante la ejecución (tiempo de ejecución). Adopta el estilo C ++ 11/14/17 en tu respuesta. Saludos ;-)
olibre

Además: ¿es compatible con tu macro enum class MyEnum : short { A, B, C };?
olibre

0

mi solución es sin uso de macro.

ventajas:

  • ves exactamente lo que haces
  • el acceso es con mapas hash, tan bueno para muchas enumeraciones valiosas
  • no es necesario considerar el orden o los valores no consecutivos
  • tanto enum a string como string a enum translation, mientras que el valor de enum agregado debe agregarse solo en un lugar adicional

desventajas:

  • necesita replicar todos los valores de enumeraciones como texto
  • el acceso en el mapa hash debe considerar mayúsculas y minúsculas
  • mantenimiento si agregar valores es doloroso: debe agregar tanto enum como en el mapa de traducción directa

así que ... hasta el día en que C ++ implemente la funcionalidad C # Enum.Parse, estaré atrapado con esto:

            #include <unordered_map>

            enum class Language
            { unknown, 
                Chinese, 
                English, 
                French, 
                German
                // etc etc
            };

            class Enumerations
            {
            public:
                static void fnInit(void);

                static std::unordered_map <std::wstring, Language> m_Language;
                static std::unordered_map <Language, std::wstring> m_invLanguage;

            private:
                static void fnClear();
                static void fnSetValues(void);
                static void fnInvertValues(void);

                static bool m_init_done;
            };

            std::unordered_map <std::wstring, Language> Enumerations::m_Language = std::unordered_map <std::wstring, Language>();
            std::unordered_map <Language, std::wstring> Enumerations::m_invLanguage = std::unordered_map <Language, std::wstring>();

            void Enumerations::fnInit()
            {
                fnClear();
                fnSetValues();
                fnInvertValues();
            }

            void Enumerations::fnClear()
            {
                m_Language.clear();
                m_invLanguage.clear();
            }

            void Enumerations::fnSetValues(void)
            {   
                m_Language[L"unknown"] = Language::unknown;
                m_Language[L"Chinese"] = Language::Chinese;
                m_Language[L"English"] = Language::English;
                m_Language[L"French"] = Language::French;
                m_Language[L"German"] = Language::German;
                // and more etc etc
            }

            void Enumerations::fnInvertValues(void)
            {
                for (auto it = m_Language.begin(); it != m_Language.end(); it++)
                {
                    m_invLanguage[it->second] = it->first;
                }
            }

            // usage -
            //Language aLanguage = Language::English;
            //wstring sLanguage = Enumerations::m_invLanguage[aLanguage];

            //wstring sLanguage = L"French" ;
            //Language aLanguage = Enumerations::m_Language[sLanguage];

0

Bueno, otra opción más. Un caso de uso típico es cuando necesita constantes para los verbos HTTP, así como el uso de sus valores de versión de cadena.

El ejemplo:

int main () {

  VERB a = VERB::GET;
  VERB b = VERB::GET;
  VERB c = VERB::POST;
  VERB d = VERB::PUT;
  VERB e = VERB::DELETE;


  std::cout << a.toString() << std::endl;

  std::cout << a << std::endl;

  if ( a == VERB::GET ) {
    std::cout << "yes" << std::endl;
  }

  if ( a == b ) {
    std::cout << "yes" << std::endl;
  }

  if ( a != c ) {
    std::cout << "no" << std::endl;
  }

}

La clase VERBO:

// -----------------------------------------------------------
// -----------------------------------------------------------
class VERB {

private:

  // private constants
  enum Verb {GET_=0, POST_, PUT_, DELETE_};

  // private string values
  static const std::string theStrings[];

  // private value
  const Verb value;
  const std::string text;

  // private constructor
  VERB (Verb v) :
  value(v), text (theStrings[v])
  {
    // std::cout << " constructor \n";
  }

public:

  operator const char * ()  const { return text.c_str(); }

  operator const std::string ()  const { return text; }

  const std::string toString () const { return text; }

  bool operator == (const VERB & other) const { return (*this).value == other.value; }

  bool operator != (const VERB & other) const { return ! ( (*this) == other); }

  // ---

  static const VERB GET;
  static const VERB POST;
  static const VERB PUT;
  static const VERB DELETE;

};

const std::string VERB::theStrings[] = {"GET", "POST", "PUT", "DELETE"};

const VERB VERB::GET = VERB ( VERB::Verb::GET_ );
const VERB VERB::POST = VERB ( VERB::Verb::POST_ );
const VERB VERB::PUT = VERB ( VERB::Verb::PUT_ );
const VERB VERB::DELETE = VERB ( VERB::Verb::DELETE_ );
// end of file

1
Para reducir el uso de memoria, puede reemplazar miembro const std::string textsimplemente theStrings[v]. Sin embargo, la pregunta es sobre las características de C ++ 11 / C ++ 14 / C ++ 17 / C ++ 20 para evitar tener que escribir tal clase a mano: - /
olibre

0

Mi respuesta esta aquí.

Puede obtener nombres de valores de enumeración y estos índices simultáneamente como deque de cadena.

Este método solo necesita poca copia, pegar y editar.

El resultado obtenido necesita una conversión de tipo de size_t a tipo de clase enum cuando necesita un valor de tipo de clase enum, pero creo que es una forma muy portátil y poderosa de tratar la clase enum.

enum class myenum
{
  one = 0,
  two,
  three,
};

deque<string> ssplit(const string &_src, boost::regex &_re)
{
  boost::sregex_token_iterator it(_src.begin(), _src.end(), _re, -1);
  boost::sregex_token_iterator e;
  deque<string> tokens;
  while (it != e)
    tokens.push_back(*it++);
  return std::move(tokens);
}

int main()
{
  regex re(",");
  deque<string> tokens = ssplit("one,two,three", re);
  for (auto &t : tokens) cout << t << endl;
    getchar();
  return 0;
}

0

Podrías usar una biblioteca de reflexión, como Ponder :

enum class MyEnum
{
    Zero = 0,
    One  = 1,
    Two  = 2
};

ponder::Enum::declare<MyEnum>()
    .value("Zero", MyEnum::Zero)
    .value("One",  MyEnum::One)
    .value("Two",  MyEnum::Two);

ponder::EnumObject zero(MyEnum::Zero);

zero.name(); // -> "Zero"

0

(Análogo de https://stackoverflow.com/a/54967187/2338477 , ligeramente modificado).

Aquí está mi propia solución con un mínimo de definición de magia y soporte de asignaciones de enumeraciones individuales.

Aquí está el archivo de encabezado:

#pragma once
#include <string>
#include <map>
#include <regex>

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

//
//  Just a container for each enumeration type.
//
template <class Enum>
class EnumReflectBase
{
public:
    static std::map<std::string, int> enum2int;
    static std::map<int, std::string> int2enum;

    static void EnsureEnumMapReady( const char* enumsInfo )
    {
        if (*enumsInfo == 0 || enum2int.size() != 0 )
            return;

        // Should be called once per each enumeration.
        std::string senumsInfo(enumsInfo);
        std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *");     // C++ identifier to optional " = <value>"
        std::smatch sm;
        int value = 0;

        for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
        {
            string enumName = sm[1].str();
            string enumValue = sm[2].str();

            if (enumValue.length() != 0)
                value = atoi(enumValue.c_str());

            enum2int[enumName] = value;
            int2enum[value] = enumName;
        }
    }
};

template <class Enum>
std::map<std::string, int> EnumReflectBase<Enum>::enum2int;

template <class Enum>
std::map<int, std::string> EnumReflectBase<Enum>::int2enum;


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




/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3 = 5,

    // 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...");
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& int2enum = EnumReflect<T>::int2enum;
    auto it = int2enum.find(t);

    if (it == int2enum.end())
        return "";

    return it->second;
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& enum2int = EnumReflect<T>::enum2int;
    auto it = enum2int.find(enumName);

    if (it == enum2int.end())
        return false;

    t = (T) it->second;
    return true;
}

Y aquí hay una aplicación de prueba de ejemplo:

DECLARE_ENUM(TestEnum,
    ValueOne,
    ValueTwo,
    ValueThree = 5,
    ValueFour = 7
);

DECLARE_ENUM(TestEnum2,
    ValueOne2 = -1,
    ValueTwo2,
    ValueThree2 = -4,
    ValueFour2
);

void main(void)
{
    string sName1 = EnumToString(ValueOne);
    string sName2 = EnumToString(ValueTwo);
    string sName3 = EnumToString(ValueThree);
    string sName4 = EnumToString(ValueFour);

    TestEnum t1, t2, t3, t4, t5 = ValueOne;
    bool b1 = StringToEnum(sName1.c_str(), t1);
    bool b2 = StringToEnum(sName2.c_str(), t2);
    bool b3 = StringToEnum(sName3.c_str(), t3);
    bool b4 = StringToEnum(sName4.c_str(), t4);
    bool b5 = StringToEnum("Unknown", t5);

    string sName2_1 = EnumToString(ValueOne2);
    string sName2_2 = EnumToString(ValueTwo2);
    string sName2_3 = EnumToString(ValueThree2);
    string sName2_4 = EnumToString(ValueFour2);

    TestEnum2 t2_1, t2_2, t2_3, t2_4, t2_5 = ValueOne2;
    bool b2_1 = StringToEnum(sName2_1.c_str(), t2_1);
    bool b2_2 = StringToEnum(sName2_2.c_str(), t2_2);
    bool b2_3 = StringToEnum(sName2_3.c_str(), t2_3);
    bool b2_4 = StringToEnum(sName2_4.c_str(), t2_4);
    bool b2_5 = StringToEnum("Unknown", t2_5);

La versión actualizada del mismo archivo de encabezado se mantendrá aquí:

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


-5

¿Qué pasa con una simple sobrecarga de transmisión? Todavía tiene que mantener el mapeo si no quiere hacer algo de macro magia, pero lo encuentro más limpio que su solución original.

#include <cstdint>  // for std::uint_fast8_t
#include <array>
#include <string>
#include <iostream>

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

std::ostream& operator<<(std::ostream& str, MyEnum type)
{
    switch(type)
    {
    case MyEnum::AAA: str << "AAA"; break;
    case MyEnum::BBB: str << "BBB"; break;
    case MyEnum::CCC: str << "CCC"; break;
    default: break;
    }
    return str;
}

int main()
{
   std::cout << MyEnum::AAA <<'\n';
}

55
1) crea aún más duplicación 2) te obliga a usar transmisiones
Karoly Horvath

66
-1 Lo siento @dau_sama, pero el propósito de todas estas preguntas recurrentes de enum a string es evitar el mantenimiento de la asignación de enum / string. Si cree que su respuesta no se ajusta al propósito, considere eliminar la respuesta. Buena suerte en tu próxima respuesta;) Saludos
olibre

-9

¿La forma más fácil?
Usa Ada: Enumeration'Image( Value )hace exactamente lo que quieres. Si realmente necesita C ++, puede intentar exportar la función:

Function To_String( Input : Enumeration ) return Interfaces.C.Strings.chars_ptr is
    ( Interfaces.C.Strings.New_String( Enumeration'Image(Input) ) )
    with Export, Convention => C;

44
¿Cómo responde esto a la pregunta? La pregunta establece claramente la conversión de una enumeración a una cadena en C ++ moderno.
Michael Choi

1
@MichaelChoi: lo hace, pero también está el problema de usar la herramienta adecuada para el trabajo: solo porque C ++ esté completo y, por lo tanto, puede resolver todos los problemas solucionables NO significa que la solución sea: rápida, fácil de mantener o eficiente. Usar un lenguaje que tenga la funcionalidad adecuada / deseada y exportarlo es una solución válida.
Shark8

3
En la primera oración de la pregunta "esta pregunta es sobre el uso de las nuevas características de C ++". entonces "[Todavía no encontré ninguna] forma elegante usando las nuevas características de C ++ 11, C ++ 14 o C ++ 17". El autor claramente estaba buscando una solución C ++; usted dio una solución en Ada, por lo que no respondió la pregunta. Sugiere incorporar una dependencia completamente diferente para resolver algo que probablemente no estaba en el alcance de las preguntas.
Michael Choi
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.