¿Hay una manera genérica para echar int
a enum
en C++
?
Si int
cae en el rango de un enum
, debería devolver un enum
valor; de lo contrario, arroje un exception
. ¿Hay alguna forma de escribirlo genéricamente ? Se enum type
debería admitir más de uno .
Antecedentes: tengo un tipo de enumeración externa y no control sobre el código fuente. Me gustaría almacenar este valor en una base de datos y recuperarlo.
enum e{x = 10000};
¿En este caso9999
cae dentro del rango deenum
?9999
no cae.enum class
.Respuestas:
Lo obvio es anotar tu enumeración:
// generic code #include <algorithm> template <typename T> struct enum_traits {}; template<typename T, size_t N> T *endof(T (&ra)[N]) { return ra + N; } template<typename T, typename ValType> T check(ValType v) { typedef enum_traits<T> traits; const T *first = traits::enumerators; const T *last = endof(traits::enumerators); if (traits::sorted) { // probably premature optimization if (std::binary_search(first, last, v)) return T(v); } else if (std::find(first, last, v) != last) { return T(v); } throw "exception"; } // "enhanced" definition of enum enum e { x = 1, y = 4, z = 10, }; template<> struct enum_traits<e> { static const e enumerators[]; static const bool sorted = true; }; // must appear in only one TU, // so if the above is in a header then it will need the array size const e enum_traits<e>::enumerators[] = {x, y, z}; // usage int main() { e good = check<e>(1); e bad = check<e>(2); }
Necesita que la matriz se mantenga actualizada
e
, lo cual es una molestia si no es el autor dee
. Como dice Sjoerd, probablemente se pueda automatizar con cualquier sistema de compilación decente.En cualquier caso, te enfrentas a 7.2 / 6:
Por lo tanto, si no es el autor de
e
, puede que tenga o no una garantía de que los valores válidos dee
realmente aparezcan en su definición.fuente
Feo.
enum MyEnum { one = 1, two = 2 }; MyEnum to_enum(int n) { switch( n ) { case 1 : return one; case 2 : return two; } throw something(); }
Ahora la verdadera pregunta. ¿Por qué necesitas esto? El código es feo, no es fácil de escribir (*?) Y no es fácil de mantener, y no es fácil de incorporar a su código. El código te dice que está mal. ¿Por qué luchar?
EDITAR:
Alternativamente, dado que las enumeraciones son tipos integrales en C ++:
enum my_enum_val = static_cast<MyEnum>(my_int_val);
pero esto es aún más feo que el anterior, mucho más propenso a errores y no lanzará como usted desea.
fuente
throw
(o hará algo especial) para tipos no válidos debe tener un interruptor como el que he publicado.static_cast<MyEnum>
también funcionará, y debería preferirse sobre elreinterpret_cast<MyEnum>
Si, como describe, los valores están en una base de datos, ¿por qué no escribir un generador de código que lea esta tabla y cree un archivo .hy .cpp con la enumeración y la
to_enum(int)
función?Ventajas:
to_string(my_enum)
función.fuente
to_enum(int)
función basada en eso.make
, puede comparar la fecha de dos archivos para ver si el generador debe volver a ejecutarse.No, no hay introspección en C ++, ni hay ninguna función de "verificación de dominio" incorporada.
fuente
¿Qué piensas acerca de esto?
#include <iostream> #include <stdexcept> #include <set> #include <string> using namespace std; template<typename T> class Enum { public: static void insert(int value) { _set.insert(value); } static T buildFrom(int value) { if (_set.find(value) != _set.end()) { T retval; retval.assign(value); return retval; } throw std::runtime_error("unexpected value"); } operator int() const { return _value; } private: void assign(int value) { _value = value; } int _value; static std::set<int> _set; }; template<typename T> std::set<int> Enum<T>::_set; class Apples: public Enum<Apples> {}; class Oranges: public Enum<Oranges> {}; class Proxy { public: Proxy(int value): _value(value) {} template<typename T> operator T() { T theEnum; return theEnum.buildFrom(_value); } int _value; }; Proxy convert(int value) { return Proxy(value); } int main() { Apples::insert(4); Apples::insert(8); Apples a = convert(4); // works std::cout << a << std::endl; // prints 4 try { Apples b = convert(9); // throws } catch (std::exception const& e) { std::cout << e.what() << std::endl; // prints "unexpected value" } try { Oranges b = convert(4); // also throws } catch (std::exception const& e) { std::cout << e.what() << std::endl; // prints "unexpected value" } }
Luego puede usar el código que publiqué aquí para activar los valores.
fuente
Apples::insert(4)
algo, por lo que esto no tiene ninguna ventaja sobre un interruptor.No debería querer que exista algo como lo que describe, me temo que hay problemas en el diseño de su código.
Además, asume que las enumeraciones vienen en un rango, pero ese no siempre es el caso:
enum Flags { one = 1, two = 2, four = 4, eigh = 8, big = 2000000000 };
Esto no está en un rango: incluso si fuera posible, ¿se supone que debe verificar cada número entero de 0 a 2 ^ n para ver si coinciden con el valor de alguna enumeración?
fuente
Si está preparado para enumerar sus valores de enumeración como parámetros de plantilla, puede hacerlo en C ++ 11 con plantillas varadic. Puede ver esto como algo bueno, ya que le permite aceptar subconjuntos de los valores de enumeración válidos en diferentes contextos; a menudo es útil al analizar códigos de fuentes externas.
Quizás no sea tan genérico como le gustaría, pero el código de verificación en sí es generalizado, solo necesita especificar el conjunto de valores. Este enfoque maneja huecos, valores arbitrarios, etc.
template<typename EnumType, EnumType... Values> class EnumCheck; template<typename EnumType> class EnumCheck<EnumType> { public: template<typename IntType> static bool constexpr is_value(IntType) { return false; } }; template<typename EnumType, EnumType V, EnumType... Next> class EnumCheck<EnumType, V, Next...> : private EnumCheck<EnumType, Next...> { using super = EnumCheck<EnumType, Next...>; public: template<typename IntType> static bool constexpr is_value(IntType v) { return v == static_cast<typename std::underlying_type<EnumType>::type>(V) || super::is_value(v); } EnumType convert(IntType v) { if (!is_value(v)) throw std::runtime_error("Enum value out of range"); return static_cast<EnumType>(v); }; enum class Test { A = 1, C = 3, E = 5 }; using TestCheck = EnumCheck<Test, Test::A, Test::C, Test::E>; void check_value(int v) { if (TestCheck::is_value(v)) printf("%d is OK\n", v); else printf("%d is not OK\n", v); } int main() { for (int i = 0; i < 10; ++i) check_value(i); }
fuente
C ++ 0x alternativa a la versión "fea", permite múltiples enumeraciones. Utiliza listas de inicializadores en lugar de conmutadores, un poco más limpio en mi opinión. Desafortunadamente, esto no soluciona la necesidad de codificar los valores de enumeración.
#include <cassert> // assert namespace // unnamed namespace { enum class e1 { value_1 = 1, value_2 = 2 }; enum class e2 { value_3 = 3, value_4 = 4 }; template <typename T> int valid_enum( const int val, const T& vec ) { for ( const auto item : vec ) if ( static_cast<int>( item ) == val ) return val; throw std::exception( "invalid enum value!" ); // throw something useful here } // valid_enum } // ns int main() { // generate list of valid values const auto e1_valid_values = { e1::value_1, e1::value_2 }; const auto e2_valid_values = { e2::value_3, e2::value_4 }; auto result1 = static_cast<e1>( valid_enum( 1, e1_valid_values ) ); assert( result1 == e1::value_1 ); auto result2 = static_cast<e2>( valid_enum( 3, e2_valid_values ) ); assert( result2 == e2::value_3 ); // test throw on invalid value try { auto result3 = static_cast<e1>( valid_enum( 9999999, e1_valid_values ) ); assert( false ); } catch ( ... ) { assert( true ); } }
fuente