Есть ли универсальный способ бросить int
на enum
в C++
?
Если int
попадает в диапазон, enum
он должен вернуть enum
значение, в противном случае выбросить exception
. Есть ли способ написать это в общем виде ? enum type
Следует поддерживать более одного .
Предыстория: у меня есть внешний тип перечисления, и я не могу контролировать исходный код. Я хотел бы сохранить это значение в базе данных и получить его.
enum e{x = 10000};
9999
попадает ли в этом случае в диапазонenum
?9999
не падает.enum class
.Ответы:
Очевидно, что нужно аннотировать ваше перечисление:
// 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); }
Вам необходимо регулярно обновлять массив
e
, что является неприятностью, если вы не являетесь авторомe
. Как говорит Сьорд, его, вероятно, можно автоматизировать с помощью любой достойной системы сборки.В любом случае, вы против 7,2 / 6:
Поэтому, если вы не являетесь автором
e
, у вас может быть или не быть гарантии, что действительные значенияe
действительно присутствуют в его определении.источник
Некрасиво.
enum MyEnum { one = 1, two = 2 }; MyEnum to_enum(int n) { switch( n ) { case 1 : return one; case 2 : return two; } throw something(); }
А теперь настоящий вопрос. Зачем тебе это нужно? Код уродливый, его нелегко написать (*?), Нелегко поддерживать и нелегко включить в ваш код. Код, который говорит вам, что это неправильно. Зачем с этим бороться?
РЕДАКТИРОВАТЬ:
В качестве альтернативы, учитывая, что перечисления являются целыми типами в C ++:
enum my_enum_val = static_cast<MyEnum>(my_int_val);
но это еще более уродливо, чем выше, гораздо более подвержено ошибкам, и он не будет бросать так, как вы хотите.
источник
throw
(или сделает что-то особенное) для недопустимых типов, должно иметь переключатель, как я опубликовал.static_cast<MyEnum>
будет работать, и его следует предпочестьreinterpret_cast<MyEnum>
Если, как вы описываете, значения находятся в базе данных, почему бы не написать генератор кода, который считывает эту таблицу и создает файлы .h и .cpp как с перечислением, так и с
to_enum(int)
функцией?Преимущества:
to_string(my_enum)
функцию.источник
to_enum(int)
функцию на его основе.make
может даже сравнить дату двух файлов, чтобы определить, нужно ли перезапускать генератор.Нет, в C ++ нет ни интроспекции, ни встроенного средства «проверки домена».
источник
Что ты думаешь об этом?
#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" } }
Затем вы можете использовать код, который я разместил здесь, для включения значений.
источник
Apples::insert(4)
то добавить , так что это не имеет преимущества перед переключателем.Вы не должны хотеть существования чего-то вроде того, что вы описываете, я боюсь, что в вашем дизайне кода есть проблемы.
Кроме того, вы предполагаете, что перечисления входят в диапазон, но это не всегда так:
enum Flags { one = 1, two = 2, four = 4, eigh = 8, big = 2000000000 };
Это не входит в диапазон: даже если бы это было возможно, вы должны проверять каждое целое число от 0 до 2 ^ n, чтобы увидеть, соответствуют ли они некоторому значению перечисления?
источник
Если вы готовы указать значения перечисления в качестве параметров шаблона, вы можете сделать это в C ++ 11 с помощью шаблонов varadic. Вы можете рассматривать это как хорошую вещь, позволяющую принимать подмножества допустимых значений перечисления в разных контекстах; часто используется при разборе кодов из внешних источников.
Возможно, не такой общий, как хотелось бы, но сам код проверки является обобщенным, вам просто нужно указать набор значений. Этот подход обрабатывает пробелы, произвольные значения и т. Д.
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); }
источник
Альтернатива C ++ 0x «уродливой» версии, позволяет использовать несколько перечислений. Использует списки инициализаторов, а не переключатели, немного чище IMO. К сожалению, это не помогает обойти необходимость жесткого кодирования значений перечисления.
#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 ); } }
источник