Czy istnieje sposób ogólny do oddania int
do enum
w C++
?
Jeśli int
mieści się w zakresie an enum
, powinien zwrócić enum
wartość, w przeciwnym razie należy zgłosić exception
. Czy jest sposób, aby napisać to ogólnie ? enum type
Należy wspierać więcej niż jeden .
Tło: mam zewnętrzny typ wyliczenia i nie mam kontroli nad kodem źródłowym. Chciałbym przechowywać tę wartość w bazie danych i ją odzyskać.
enum e{x = 10000};
czy w tym przypadku9999
mieści się w zakresieenum
?9999
nie spada.enum class
.Odpowiedzi:
Oczywistą rzeczą jest dodanie adnotacji do wyliczenia:
// 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); }
Musisz aktualizować tablicę
e
, co jest uciążliwe, jeśli nie jesteś jej autoreme
. Jak mówi Sjoerd, prawdopodobnie można to zautomatyzować za pomocą dowolnego porządnego systemu kompilacji.W każdym razie masz do czynienia z 7,2 / 6:
Więc jeśli nie jesteś autorem
e
, możesz lub nie mieć gwarancji, że prawidłowe wartoście
rzeczywiście pojawiają się w jego definicji.źródło
Brzydki.
enum MyEnum { one = 1, two = 2 }; MyEnum to_enum(int n) { switch( n ) { case 1 : return one; case 2 : return two; } throw something(); }
A teraz prawdziwe pytanie. Dlaczego tego potrzebujesz? Kod jest brzydki, niełatwy do napisania (*?), Niełatwy w utrzymaniu i niełatwy do włączenia do kodu. Kod, który mówi ci, że jest zły. Po co z tym walczyć?
EDYTOWAĆ:
Alternatywnie, biorąc pod uwagę, że wyliczenia są typami całkowitymi w C ++:
enum my_enum_val = static_cast<MyEnum>(my_int_val);
ale to jest jeszcze brzydsze niż powyżej, znacznie bardziej podatne na błędy i nie wyrzuci tak, jak chcesz.
źródło
throw
(lub zrobi coś specjalnego) dla nieprawidłowych typów, musi mieć przełącznik, tak jak opublikowałem.static_cast<MyEnum>
również się sprawdzi i powinno być preferowane nadreinterpret_cast<MyEnum>
Jeśli, jak opisujesz, wartości znajdują się w bazie danych, dlaczego nie napisać generatora kodu, który odczytuje tę tabelę i tworzy plik .h i .cpp z wyliczeniem i
to_enum(int)
funkcją?Zalety:
to_string(my_enum)
funkcję.źródło
to_enum(int)
podstawie funkcję.make
może porównać datę dwóch plików, aby sprawdzić, czy generator musi zostać ponownie uruchomiony.Nie - w C ++ nie ma introspekcji ani wbudowanej funkcji „sprawdzania domeny”.
źródło
Co o tym myślisz?
#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" } }
Następnie możesz użyć kodu, który tutaj opublikowałem , aby włączyć wartości.
źródło
Apples::insert(4)
gdzieś dodać , więc nie ma to przewagi nad przełącznikiem.Nie powinieneś chcieć, aby coś takiego jak to, co opisujesz, istniało, obawiam się, że są problemy w projekcie twojego kodu.
Zakładasz również, że wyliczenia mieszczą się w zakresie, ale nie zawsze tak jest:
enum Flags { one = 1, two = 2, four = 4, eigh = 8, big = 2000000000 };
To nie jest w zakresie: nawet gdyby było to możliwe, czy powinieneś sprawdzić każdą liczbę całkowitą od 0 do 2 ^ n, aby zobaczyć, czy pasuje do jakiejś wartości wyliczenia?
źródło
Jeśli jesteś przygotowany do umieszczenia wartości wyliczenia jako parametrów szablonu, możesz to zrobić w C ++ 11 za pomocą szablonów waradycznych. Możesz potraktować to jako dobrą rzecz, umożliwiając akceptowanie podzbiorów prawidłowych wartości wyliczenia w różnych kontekstach; często przydatne podczas analizowania kodów ze źródeł zewnętrznych.
Być może nie jest tak ogólny, jak byś chciał, ale sam kod sprawdzający jest uogólniony, wystarczy określić zestaw wartości. To podejście obsługuje luki, arbitralne wartości itp.
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); }
źródło
C ++ 0x alternatywa dla "brzydkiej" wersji, pozwala na wiele wyliczeń. Używa list inicjalizujących zamiast przełączników, nieco czystszy IMO. Niestety, nie pozwala to obejść potrzeby sztywnego kodowania wartości wyliczeniowych.
#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 ); } }
źródło