enum para encadenar en C ++ 11 / C ++ 14 / C ++ 17 moderno y futuro C ++ 20

Contrariamente a todas las demás preguntas similares, esta pregunta se trata de usar las nuevas características de C ++.

  • 2008 c ¿Hay una forma simple de convertir C ++ enum a string?
  • 2008 c ¿ Manera fácil de usar variables de tipos enum como cadena en C?
  • 2008 c ++ Cómo mapear fácilmente enums de C ++ a cadenas
  • 2008 c ++ ¿ Haciendo algo tanto un identificador C como una cadena?
  • 2008 c ++ ¿Hay un script simple para convertir C ++ enum a string?
  • 2009 c ++ ¿Cómo usar las enumeraciones como banderas en C ++?
  • 2011 c ++ ¿Cómo convertir una variable tipo enum a una cadena?
  • 2011 c ++ Enum a cadena C ++
  • 2011 c ++ ¿Cómo convertir una variable tipo enum a una cadena?
  • 2012 c Cómo convertir nombres enum a cadena en c
  • 2013 c Stringifying una enumeración comstackda condicionalmente en C

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

  • Manera elegante que usa C ++ 11 , C ++ 14 o C ++ 17 nuevas características
  • O algo listo para usar en Boost
  • De lo contrario, algo planeado para C ++ 20

Ejemplo

Un ejemplo es a menudo mejor que una explicación larga.
Puede comstackr y ejecutar este fragmento en Coliru .
( Otro ejemplo anterior también está disponible)

#include  #include  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 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 invaluable de otras respuestas o enlace básico .
  • Por favor, evite la respuesta basada en macros de macro, o intente reducir la sobrecarga de #define lo mínimo posible.
  • Por favor, no enum manual -> mapeo de string .

Agradable tener

  • Apoye valores enum partir de un número diferente de cero
  • Soporta valores de enum negativos
  • Apoyar valores enum fragmentados
  • class enum soporte (C ++ 11)
  • class enum : soporte class enum : con cualquier permitido (C ++ 11)
  • Conversiones en tiempo de comstackció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::map no es una gran idea …)
  • constexpr (C ++ 11, relajado en C ++ 14)
  • noexcept (C ++ 11)
  • fragmento C ++ 14 / C ++ 17 amigable
  • C ++ Estado de la técnica

Una posible idea podría ser utilizar las capacidades del comstackdor de C ++ para generar código C ++ en tiempo de comstackción utilizando trucos de meta-progtwigción basados ​​en variadic template class constexpr y funciones constexpr

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 servirá, pero personalmente no me gusta para mayor claridad)
  • ‘*’ para obtener el recuento de enumeraciones

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

Código:

 #include  #include  #include  #include  #include  #include  #define STRING_REMOVE_CHAR(str, ch) str.erase(std::remove(str.begin(), str.end(), ch), str.end()) std::vector splitString(std::string str, char sep = ',') { std::vector 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 E##MapName(generateEnumMap(#__VA_ARGS__)); \ std::ostream &operator<<(std::ostream &os, E enumTmp) \ { \ os << E##MapName[static_cast(enumTmp)]; \ return os; \ } \ size_t operator*(E enumTmp) { (void) enumTmp; return E##MapName.size(); } \ std::string operator~(E enumTmp) { return E##MapName[static_cast(enumTmp)]; } \ std::string operator+(std::string &&str, E enumTmp) { return str + E##MapName[static_cast(enumTmp)]; } \ std::string operator+(E enumTmp, std::string &&str) { return E##MapName[static_cast(enumTmp)] + str; } \ std::string &operator+=(std::string &str, E enumTmp) \ { \ str += E##MapName[static_cast(enumTmp)]; \ return str; \ } \ E operator++(E &enumTmp) \ { \ auto iter = E##MapName.find(static_cast(enumTmp)); \ if (iter == E##MapName.end() || std::next(iter) == E##MapName.end()) \ iter = E##MapName.begin(); \ else \ { \ ++iter; \ } \ enumTmp = static_cast(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  std::map generateEnumMap(std::string strMap) { STRING_REMOVE_CHAR(strMap, ' '); STRING_REMOVE_CHAR(strMap, '('); std::vector enumTokens(splitString(strMap)); std::map 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 enumNameValue(splitString(*iter, '=')); enumName = enumNameValue[0]; //inxMap = static_cast(enumNameValue[1]); if (std::is_unsigned::value) { inxMap = static_cast(std::stoull(enumNameValue[1], 0, 0)); } else { inxMap = static_cast(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(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; } 

Puede ejecutar el código aquí

(El enfoque de la biblioteca better_enums )

Hay una forma de hacer enumerar cadena 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 se pueden hacer constexpr . También puede implementar la propuesta de reflexión C ++ 17 mencionada en la respuesta por @ecatmur.

  • Solo hay una macro. Creo que este es el mínimo posible, porque la cadena de 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 statement enum incorporada. Esto significa que tienen la misma syntax 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 restricciones de espacio en StackOverflow.
  • También hay un artículo de CodeProject que describe los aspectos básicos de la implementación en un tutorial de larga duración. [ ¿Debo moverlo aquí? Creo que es demasiado para una respuesta SO ].
  • Hay una biblioteca con todas las funciones "Better Enums" que implementa la macro en un único archivo de encabezado. También implementa N4428 Type Property Queries , la revisión actual de la propuesta de reflexión C ++ 17 N4113. Por lo tanto, al menos para enumeraciones declaradas a través de esta macro, puede tener la reflexión enum propuesta de C ++ 17 ahora, en C ++ 11 / C ++ 14.

Es fácil extender esta respuesta a las características de la biblioteca: nada "importante" queda aquí. Sin embargo, es bastante tedioso, y hay preocupaciones de portabilidad del comstackdor.

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 usarlo como N4428 , que explica la porción de enumeraciones de esa propuesta.


Explicación

El siguiente código implementa conversiones entre enumeraciones y cadenas. Sin embargo, puede ampliarse para hacer otras cosas también, como la iteración. Esta respuesta envuelve una enumeración en una struct . También puede generar una struct rasgos 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 como {Red = 1, Green, Blue} como inicializador para la matriz de valores. Esto no es válido en C ++, porque Red no es una expresión asignable. Esto se resuelve lanzando cada constante a un tipo T que tiene un operador de asignación, pero abandonará la asignación: {(T)Red = 1, (T)Green, (T)Blue} .
  2. Del mismo modo, terminaremos con {"Red = 1", "Green", "Blue"} como el inicializador para la matriz de nombres. Tendremos que recortar el " = 1" . No conozco una forma excelente de hacerlo en tiempo de comstackción, por lo que pospondremos esto al tiempo de ejecución. Como resultado, _to_string no será constexpr , pero _from_string aún puede ser constexpr , porque podemos tratar los espacios en blanco e igualar los signos como terminadores al comparar con cadenas no recortadas.
  3. Los dos anteriores necesitan una macro "mapeo" que pueda aplicar otra macro a cada elemento en __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, no necesita declarar 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 ) en el ámbito del espacio de nombres, o arreglos regulares en funciones en línea estáticas no constexpr . 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  // For size_t. #include  // For strcspn, strncpy. #include  // 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  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 comstacktion 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  #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 progtwig de arriba muestra 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 switch dará como resultado una advertencia del comstackdor (dependiendo de su comstackdor y de sus indicadores). Además, tenga en cuenta que "Red" se convirtió en una enumeración durante la comstackción.

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

A partir de P0194r2 (2016-10-15), la syntax utilizaría la palabra clave reflexpr propuesta:

 meta::get_base_name_v< meta::get_element_m< meta::get_enumerators_m, 0> > 

Por ejemplo (adaptado de la twig refleja de clang de Matus Choclik ):

 #include  #include  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, 0> >; // prints "AAA" std::cout << name_of_MyEnum_0 << std::endl; } 

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

En particular, el grupo de estudio Reflection revisó la última propuesta fusionada de reflexión estática 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 siguiente estándar.

En 2011 pasé un fin de semana ajustando una solución basada en macro y terminé nunca usándola.

Mi procedimiento actual es iniciar Vim, copiar los enumeradores en un cuerpo de conmutador vacío, iniciar una macro nueva, transformar el primer enumerador en una sentencia case, mover el cursor al principio 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(ethertype); } 

Y así es como me las arreglo.

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

Una forma alternativa de hacerlo fue publicada por @sehe en los comentarios .

No sé si te va a gustar o no, no estoy muy contento con esta solución, pero es un enfoque amistoso para C ++ 14 porque usa variables de plantilla y abusa de la especialización de plantillas:

 enum class MyEnum : std::uint_fast8_t { AAA, BBB, CCC, }; template const char MyEnumName[] = "Invalid MyEnum value"; template<> const char MyEnumName[] = "AAA"; template<> const char MyEnumName[] = "BBB"; template<> const char MyEnumName[] = "CCC"; int main() { // Prints "AAA" std::cout << MyEnumName << '\n'; // Prints "Invalid MyEnum value" std::cout << MyEnumName(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 difícil de mantener, pero también es un fastidio mantener algunos de los enfoques similares, ¿verdad?

Buenos puntos sobre este enfoque:

  • Uso de tempates 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 si esto podría ser útil en absoluto).
  • Se ve limpio.
  • La búsqueda de nombre se realiza en tiempo de comstackción.

Ejemplo en vivo

Editar

Misterioso usuario673679 tienes razón; el enfoque de plantilla variable de 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 plantilla variable más trucos de plantilla variadic 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 enum-to-string:

 // enum_map contains pairs of enum value and value string for each enum // this shortcut allows us to use enum_map. template  using enum_map = std::map; // This variable template will create a map for each enum type which is // instantiated with. template  enum_map enum_values{}; 

Entonces, el engaño de la plantilla variadic:

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

El " mejor truco " aquí es el uso de una plantilla variable para el mapa que contiene los valores y los nombres de cada entrada enum; 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 función de initialize así:

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

Estamos asignando nombres a cada entrada de MyEnum y se puede usar en tiempo de ejecución:

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

Pero se puede mejorar con SFINAE y sobrecargando << operador:

 template::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.find(value); return o << (found == enum_values.end() ? Unknown : found->second); } 

Con el operator << 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.

Ejemplo en vivo

Tuve el mismo problema hace un par de días. No pude encontrar ninguna solución de C ++ sin una macro magia extraña, así que decidí escribir un generador de código CMake para generar declaraciones simples de mayúsculas y minúsculas.

Uso:

 enum2str_generate( PATH  CLASS_NAME  FUNC_NAME  NAMESPACE  INCLUDES  ENUMS  BLACKLIST  USE_CONSTEXPR  USE_C_STRINGS  ) 

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

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

Ejemplo:

./includes/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 "ah" # 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  #include  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 ""; } } /*! * \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 ""; } } } 

Actualizar:

El script ahora también admite enumeraciones con ámbito (clase enum | struct) y lo moví a un repository separado con algunos otros scripts que suelo usar: https://github.com/mensinda/cmakeBuildTools

Si tu enum ve como

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

Puede mover el contenido de la enum a 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 la enum nuevamente:

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

Y finalmente puedes generar funciones de utilidad sobre esta 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 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"); } 

The solution can be applied to older C++ standards and it does not use modern C++ elements but it can be used to generate lot of code without too much effort and maintenance.

As per request from the OP, here a stripped down version of the ugly macro solution based on Boost Preprosessor and Variadic Macros .

It allows for a simple list like syntax of the enumerator elements along with setting values for specific elements so that

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

se expande a

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

Alongside with the necessary functions to output and do some conversion back. This macro has been around here for ages, and I am not totally sure that its the most efficient way, or that it is a conforming way, but it has ever since been working

The complete code can be seen in action at both Ideone and Coliru .

Its gargantuan ugliness is above; I would have put it behind spoilers to protect your eyes, if I knew how, but markdown doesn’t like me.

The library (merged within one single header file)

 #include  #include  #include  namespace xxx { template struct enum_cast_adl_helper { }; template E enum_cast( const std::string& s ) { return do_enum_cast(s,enum_cast_adl_helper()); } template E enum_cast( const char* cs ) { std::string s(cs); return enum_cast(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& ) \ { \ static const std::unordered_map 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  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(\"b\") = " << xxx::enum_cast("b") <<'\n'; } 

Comstacktion (copy paste header within 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("b") = foo::b 

Just generate your enums. Writing a generator for that purpose is about five minutes’ work.

Generator code in java and python, super easy to port to any language you like, including C++.

Also super easy to extend by whatever functionality you want.

example input:

 First = 5 Second Third = 7 Fourth Fifth=11 

generated header:

 #include  enum class Hallo { First = 5, Second = 6, Third = 7, Fourth = 8, Fifth = 11 }; std::ostream & operator << (std::ostream &, const Hallo&); 

generated cpp file

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

And the generator, in a very terse form as a template for porting and extension. This example code really tries to avoid overwriting any files but still use it at your own risk.

 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:   "); } 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 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 "); headerWriter.println(); headerWriter.println("enum class " + enumName); headerWriter.println('{'); boolean first = true; for (Entry 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 "); 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 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 << \"\";"); 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); } } } 

And a port to Python 3.5 because different enough to be potentially helpful

 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:   ") 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 \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 \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 << \"\";\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() 

I took the idea from @antron and implemented it differently: generating a true enum class .

This implementation meets all the requirements listed in original question but currently has only one real limitation : it assumes the enum values are either not provided or, if provided, must start with 0 and go up sequentially without gaps.

This is not an intrinsic limitation – simply that I don’t use ad-hoc enum values. If this is needed, one can replace vector lookup with traditional switch/case implementation.

The solution uses some c++17 for inline variables but this can be easily avoided if needed. It also uses boost:trim because of simplicity.

Most importantly, it takes only 30 lines of code and no black magic macros. El código está abajo. It’s meant to be put in header and included in multiple comstacktion modules.

It can be used the same way as was suggested earlier in this thread:

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

Pls let me know if this is useful and how it can be improved further.


 #include  struct EnumSupportBase { static std::vector split(const std::string s, char delim) { std::stringstream ss(s); std::string item; std::vector 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 _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); \ } 

I have been frustrated by this problem for a long time too, along with the problem of getting a type converted to string in a proper way. However, for the last problem, I was surprised by the solution explained in Is it possible to print a variable’s type in standard C++? , using the idea from Can I obtain C++ type names in a constexpr way? . Using this technique, an analogous function can be constructed for getting an enum value as string:

 #include  using namespace std; class static_string { const char* const p_; const std::size_t sz_; public: typedef const char* const_iterator; template  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  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  struct EnumWrapper { template < Enum enu > static static_string name() { #ifdef __clang__ static_string p = __PRETTY_FUNCTION__; static_string enumType = typeName(); 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(); return static_string(p.data() + 57 + enumType.size(), p.size() - 57 - enumType.size() - 7); #endif } }; } /// \brief Get the name of an enum value template  static_string enumName() { return details::EnumWrapper::template name(); } enum class Color { Blue = 0, Yellow = 1 }; int main() { std::cout << "_" << typeName() << "_" << std::endl; std::cout << "_" << enumName() << "_" << std::endl; return 0; } 

The code above has only been tested on Clang (see https://ideone.com/je5Quv ) and VS2015, but should be adaptable to other compilers by fiddling a bit with the integer constants. Of course, it still uses macros under the hood, but at least one doesn't need access to the enum implementation.

The following solution is based on a std::array for a given enum.

For enum to std::string conversion we can just cast the enum to size_t and lookup the string from the array. The operation is O(1) and requires no heap allocation.

 #include  #include  #include  #include  #include  #include  #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 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(e)]; \ } \ } 

For std::string to enum conversion we would have to make a linear search over the array and cast the array index to enum .

Try it here with usage examples: http://coliru.stacked-crooked.com/a/e4212f93bee65076

Edit: Reworked my solution so the custom Enum can be used inside a class.

Solutions using enum within class/struct (struct defaults with public members) and overloaded operators:

 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 = oe; return *this; } bool operator==(Enum o) { return e == o; } bool operator==(Color o) { return e == oe; } 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"; } } }; 

From the outside it looks nearly exactly like a class enum:

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

This will output "red 1 2". You could possibly overload << to make blue output a string (although it might cause ambiguity so not possible), but it wouldn't work with Color::GREEN since it doesn't automatically convert to Color.

The purpose of having an implicit convert to Enum (which implicitly converts to int or type given) is to be able to do:

 Color color; switch (color) ... 

This works, but it also means that this work too:

 int i = color; 

With an enum class it wouldn't compile. You ought to be careful if you overload two functions taking the enum and an integer, or remove the implicit conversion...

Another solution would involve using an actual enum class and static members:

 struct Color { enum class Enum { RED, GREEN, BLUE }; static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE; //same as previous... }; 

It possibly takes more space, and is longer to make, but causes a compile error for implicit int conversions. I'd use this one because of that!

There's surely overhead with this though, but I think it's just simpler and looks better than other code I've seen. There's also potential for adding functionality, which could all be scoped within the class.

Edit : this works and most can be compiled before execution:

 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 == oe; } constexpr operator Enum() const { return e; } Color& operator=(Enum o) { const_cast(this->e) = o; return *this; } Color& operator=(Color o) { const_cast(this->e) = oe; 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; }; 

This gist provides a simple mapping based on C++ variadic templates.

This is a C++17-simplified version of the type-based map from the gist :

 #include  // http://stackoverflow.com/q/24520781 template 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::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::get(key); } }; template class names { typedef map 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); } }; 

An example usage:

 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 // C++17 template 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_names; 

The map can be used in both directions:

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

This example is available on godbolt.org

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

Result from gcc-7 -std=c++1z -Ofast -S

 main: mov eax, 3 ret 

Very simple solution with one big constraint: you can’t assign custom values to enum values, but with the right regex, you could. you could also add a map to translate them back to enum values without much more effort:

 #include  #include  #include  #include  std::vector split(const std::string& s, const std::regex& delim = std::regex(",\\s*")) { using namespace std; vector cont; copy(regex_token_iterator(s.begin(), s.end(), delim, -1), regex_token_iterator(), back_inserter(cont)); return cont; } #define EnumType(Type, ...) enum class Type { __VA_ARGS__ } #define EnumStrings(Type, ...) static const std::vector \ Type##Strings = split(#__VA_ARGS__); #define EnumToString(Type, ...) EnumType(Type, __VA_ARGS__); \ EnumStrings(Type, __VA_ARGS__) 

Ejemplo de uso:

 EnumToString(MyEnum, Red, Green, Blue); 

EDIT: check below for a newer version

As mentioned above, N4113 is the final solution to this matter, but we’ll have to wait more than a year to see it coming out.

Meanwhile, if you want such feature, you’ll need to resort to “simple” templates and some preprocessor magic.

Enumerator

 template 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 T Enum::m_counter = 0; #define ENUM_TYPE(x) using Enum = Enum; #define ENUM_DECL(x,...) x(#x,##__VA_ARGS__) #define ENUM(...) const Enum ENUM_DECL(__VA_ARGS__); 

Uso

 #include  //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 } 

Simple explaination

Enum::m_counter is set to 0 inside each namespace declaration.
( Could someone point me out where ^^this behaviour^^ is mentioned on the standard? )
The preprocessor magic automates the declaration of enumerators.

Desventajas

  • It's not a true enum type, therefore not promotable to int
  • Cannot be used in switch cases

Alternative solution

This one sacrifices line numbering (not really) but can be used on switch cases .

 #define ENUM_TYPE(x) using type = Enum #define ENUM(x) constexpr type x{__LINE__,#x} template 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 0 conflicts with -pedantic on GCC and clang.

Solución

Either start at #line 1 and subtract 1 from __LINE__ .
Or, don't use -pedantic .
And while we're at it, avoid VC++ at all costs, it has always been a joke of a compiler.

Uso

 #include  namespace Level { ENUM_TYPE(short); #line 0 ENUM(OFF); ENUM(SEVERE); ENUM(WARNING); #line 10 ENUM(INFO); ENUM(DEBUG); ENUM(ALL); #line  //restre 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; } 

Real-life implementation and use

r3dVoxel - Enum
r3dVoxel - ELoggingLevel

Referencia rápida

#line lineno -- cppreference.com

As long as you are okay with writing a separate .h/.cpp pair for each queryable enum, this solution works with nearly the same syntax and capabilities as a regular c++ enum:

 // MyEnum.h #include  #ifndef ENUM_INCLUDE_MULTI #pragma once #end if enum MyEnum : int ETRAITS { EDECL(AAA) = -8, EDECL(BBB) = '8', EDECL(CCC) = AAA + BBB }; 

The .cpp file is 3 lines of boilerplate:

 // MyEnum.cpp #define ENUM_DEFINE MyEnum #define ENUM_INCLUDE  #include  

Ejemplo de uso:

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

Código

This solution requires 2 source files:

 // EnumTraits.h #pragma once #include  #include  #include  #define ETRAITS #define EDECL(x) x template  class EnumTraits { public: static const std::vector& 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; using name_map_type = std::unordered_map; using value_map_type = std::unordered_map; 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  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; 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

This implementation exploits the fact that the braced list of elements of an enum definition can also be used as a braced initializer list for class member initialization.

When ETRAITS is evaluated in the context of EnumTraits.inl , it expands out to a static member definition for the EnumTraits<> class.

The EDECL macro transforms each enum member into initializer list values which subsequently get passed into the member constructor in order to populate the enum info.

The EnumInitGuard class is designed to consume the enum initializer values and then collapse - leaving a pure list of enum data.

Beneficios

  • c++ -like syntax
  • Works identically for both enum and enum class (*almost)
  • Works for enum types with any numeric underlying type
  • Works for enum types with automatic, explicit, and fragmented initializer values
  • Works for mass renaming (intellisense linking preserved)
  • Only 5 preprocessor symbols (3 global)

* In contrast to enums , initializers in enum class types that reference other values from the same enum must have those values fully qualified

Disbenefits

  • Requires a separate .h/.cpp pair for each queryable enum
  • Depends on convoluted macro and include magic
  • Minor syntax errors explode into much larger errors
  • Defining class or namespace scoped enums is nontrivial
  • No compile time initialization

Comentarios

Intellisense will complain a bit about private member access when opening up EnumTraits.inl , but since the expanded macros are actually defining class members, that isn't actually a problem.

The #ifndef ENUM_INCLUDE_MULTI block at the top of the header file is a minor annoyance that could probably be shrunken down into a macro or something, but it's small enough to live with at its current size.

Declaring a namespace scoped enum requires that the enum first be forward declared inside its namespace scope, then defined in the global namespace. Additionally, any enum initializers using values of the same enum must have those values fully qualified.

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

I wrote a library for solving this problem, everything happens in compiling time, except for getting the message.

Uso:

Use macro DEF_MSG to define a macro and message pair:

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

CODE_OK is the macro to use, and "OK!" is the corresponding message.

Use get_message() or just gm() to get the message:

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

Use MSG_NUM to find out how many macros have been defined. This will automatically increse, you don’t need to do anything.

Predefined messages:

 MSG_OK: OK MSG_BOTTOM: Message bottom 

Project: libcodemsg


The library doesn’t create extra data. Everything happens in compiling time. In message_def.h , it generates an enum called MSG_CODE ; in message_def.c , it generates a variable holds all the strings in static const char* _g_messages[] .

In such case, the library is limited to create one enum only. This is ideal for return values, for example:

 MSG_CODE foo(void) { return MSG_OK; // or something else } MSG_CODE ret = foo(); if (MSG_OK != ret) { printf("%s\n", gm(ret);); } 

Another thing I like this design is you can manage message definitions in different files.


I found the solution to this question looks much better.

 #define ENUM_MAKE(TYPE, ...) \ enum class TYPE {__VA_ARGS__};\ struct Helper_ ## TYPE { \ static const String& toName(TYPE type) {\ int index = static_cast(type);\ return splitStringVec()[index];}\ static const TYPE toType(const String& name){\ static std::unordered_map 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(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; 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); 

automatically ENUM_MAKE macro generate ‘enum class’ and helper class with ‘enum reflection function’.

In order to reduce mistakes, at once Everything is defined with only one ENUM_MAKE.

The advantage of this code is automatically created for reflection and a close look at macro code ,easy-to-understand code. ‘enum to string’ , ‘string to enum’ performance both is algorithm O(1).

Disadvantages is when first use , helper class for enum relection ‘s string vector and map is initialized. but If you want you’ll also be pre-initialized. –

my solution is without macro usage.

advantages:

  • you see exactly what you do
  • access is with hash maps, so good for many valued enums
  • no need to consider order or non-consecutive values
  • both enum to string and string to enum translation, while added enum value must be added in one additional place only

disadvantages:

  • you need to replicate all the enums values as text
  • access in hash map must consider string case
  • maintenance if adding values is painful – must add in both enum and direct translate map

so… until the day that C++ implements the C# Enum.Parse functionality, I will be stuck with this:

  #include  enum class Language { unknown, Chinese, English, French, German // etc etc }; class Enumerations { public: static void fnInit(void); static std::unordered_map  m_Language; static std::unordered_map  m_invLanguage; private: static void fnClear(); static void fnSetValues(void); static void fnInvertValues(void); static bool m_init_done; }; std::unordered_map  Enumerations::m_Language = std::unordered_map (); std::unordered_map  Enumerations::m_invLanguage = std::unordered_map (); 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]; 

Well, yet another option. A typical use case is where you need constants for the HTTP verbs as well as using its string version values.

The example:

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

The VERB class:

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

What about a simple streaming overload? You still have to maintain the mapping if you don’t want to do some macro magic, but I find it cleaner than your original solution.

 #include  // for std::uint_fast8_t #include  #include  #include  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'; }