resueltos programas programar principiantes para lenguaje ejercicios ejemplos desde curso como comandos cero basicos avanzados c++ enums c++14 c++20 c++17

programas - enum a string en C++ 11/C++ 14/C++ 17 moderno y C++ 20 futuro



lenguaje c (29)

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

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

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

Explicación simple

Enum<T>::m_counter se 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 enum tipo 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 se puede usar en casos de cambio .

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"

Errata

#line 0 entra -pedantic en conflicto con GCC y clang.

Solución alternativa

Comience en #line 1 y 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 "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 };

Implementación y uso en la vida real

r3dVoxel - Enum
r3dVoxel - ELoggingLevel

Referencia rápida

#line lineno - cppreference.com

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 sobrecarga #define más mínimo posible.
  • Por favor, no hay enum manual -> mapeo de string .

Agradable tener

  • Admite valores de enum partir de un número diferente de cero
  • Soporta valores de enum negativos
  • Admite valores de enum fragmentados
  • class enum soporte class enum (C ++ 11)
  • class enum : <type> soporte class enum : <type> tiene cualquier <type> permitido (C ++ 11)
  • Conversiones en tiempo de compilación (no en tiempo de ejecución) a una cadena,
    o al menos una ejecución rápida en tiempo de ejecución (por ejemplo, std::map no es una gran idea ...)
  • constexpr (C ++ 11, luego relajado en C ++ constexpr )
  • noexcept (C ++ 11)
  • Fragmento amigable C++17 / C++20

Una posible idea podría ser usar las capacidades del compilador C ++ para generar código C ++ en el momento de la compilación utilizando trucos de variadic template class basados ​​en variadic template class constexpr y constexpr funciones constexpr ...


magic_enum 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.

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

Para obtener más ejemplos, consulte el repositorio de inicio 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 [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX] debe estar en el 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_MIN y MAGIC_ENUM_RANGE_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 <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; }


¿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:

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


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


(Análogo de https://.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:

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

Y aquí hay una aplicación de prueba de 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 versión actualizada del mismo archivo de encabezado se mantendrá aquí:

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


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:

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

La clase VERBO:

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


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

Uso:

Use macro DEF_MSG para definir un macro y un par de mensajes:

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

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

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

MSG_OK: OK MSG_BOTTOM: Message bottom

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

Mensajes predefinidos:

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

Proyecto: libcodemsg

La biblioteca no crea datos adicionales. Todo sucede en tiempo de compilación. En message_def.h , genera un enum llamado 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 enum solo uno . Esto es ideal para valores de retorno, por ejemplo:

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

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.


Esta gist 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 gist :

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

Un ejemplo de uso:

main: mov eax, 3 ret

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

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

Este ejemplo está disponible en godbolt.org

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

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

EnumToString(MyEnum, Red, Green, Blue);


Este problema también me ha frustrado durante mucho tiempo, junto con el problema de convertir un tipo a cadena de manera 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 <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); / }

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.


La biblioteca magic_enum proporciona la funcionalidad enum-to-string y string-to-enum, consta de un único archivo de solo encabezado y utiliza la funcionalidad C ++ - 17. Ejemplo de uso del archivo Léame:

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


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

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

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

Para std::string que enum la 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.


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.

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


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

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

que debería imprimir

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

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 x-macro :

#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)]; / } / }

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

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

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


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

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


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

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

se expande a

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

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 dentro de un único archivo de encabezado)

> 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))); ^

Uso

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

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

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

Salida

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


Si tu enum parece

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

Puede mover el contenido de enum a un nuevo archivo:

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

Y luego los valores pueden estar rodeados por una macro:

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

El siguiente paso puede incluir los elementos en el enum nuevo:

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;

Y finalmente puede generar funciones de utilidad sobre esto enum :

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

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.


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

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

El .cpp archivo es de 3 líneas de repetitivo:

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

Ejemplo de uso:

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

Código

Esta solución requiere 2 archivos fuente:

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

...y

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 }

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 ETRAITS se evalúa en el contexto de EnumTraits.inl , se expande a una definición de miembro estático para la EnumTraits<> clase.

La EDECL macro 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 EnumInitGuard clase 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 enum y enum class (* casi)
  • Funciona para enum tipos con cualquier tipo subyacente numérico
  • Funciona para enum tipos 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 en enum class tipos que hacen referencia a otros valores de la misma enumeración deben tener esos valores completamente calificados

Desventajas

  • Requiere un .h/.cpp par separado para cada consultable enum
  • Depende de enrevesado macro y include mágico
  • Los errores menores de sintaxis explotan en errores mucho más grandes
  • La definición class o namespace enumeració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_MULTI bloque 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.

ONE TWO 13


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:

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

encabezado generado:

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

archivo cpp generado

// Name of enum auto c = Color::RED; NAMEOF_ENUM(c) -> "RED" // Name of enum nameof::nameof_enum(c) -> "RED" constexpr auto cx_name = NAMEOF_ENUM(c); static_assert("RED" == cx_name);

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.

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

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

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


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

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__);

Ejemplo de 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 }


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

Color color; switch (color) ...

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

int i = color;

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:

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

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

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

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:

#include <cstring> // http://.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); } };

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:

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;


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 utiliza 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:

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

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

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


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

Uso:

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

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:

/*! * /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>"; } } }

./CMakeLists.txt:

First = 5 Second Third = 7 Fourth Fifth=11

Genera:

./enum2Str.hpp:

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

./enum2Str.cpp:

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

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


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:

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


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:

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

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:

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

Ejemplo:

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

Puedes ejecutar el código here


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:

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

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.

Ejemplo en vivo

Editar

user673679 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:

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

Luego, el truco de plantilla variadic:

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

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 función de initialize esta manera:

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

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

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

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

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

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

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

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

Ejemplo en vivo


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 palabra clave reflexpr propuesta:

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

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

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

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.


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

Crearé esto:

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

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 comments .


(El enfoque de la biblioteca better_enums )

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

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

Uso:

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

Todas las operaciones se pueden realizar 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 stringizació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 .
  • 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 better_enums 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 una struct . También puede generar una struct rasgos junto con una enumeración.

La estrategia es generar algo como esto:

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

Los problemas son:

  1. Terminaremos con algo como {Red = 1, Green, Blue} como inicializador de la matriz de valores. Esto no es válido en C ++, porque el Red no es una expresión asignable. Esto se resuelve convirtiendo cada constante en un tipo T que tiene un operador de asignación, pero descartará la asignación: {(T)Red = 1, (T)Green, (T)Blue} .
  2. Del mismo modo, terminaremos con {"Red = 1", "Green", "Blue"} como 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_string no será constexpr , pero _from_string aún puede ser constexpr , porque podemos tratar los espacios en blanco y los signos de igual 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 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, 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 ) en el ámbito del espacio de nombres, o matrices 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

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

y

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

El programa anterior imprime en 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 compilador (dependiendo de su compilador y sus indicadores). Además, tenga en cuenta que "Red" se convirtió en una enumeración durante la compilación.


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

ejemplo

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

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