CashCow представляет достойный ответ на этот вопрос: написать собственную функцию для выполнения проверенного приведения, безусловно, просто.
К сожалению, это также большая работа, и вы должны убедиться, что она синхронизирована с перечислением, чтобы список перечислителей в определении перечисления совпадал со списком перечислителей в проверенной функции приведения. Вы также должны написать один из них для каждого перечисления, к которому вы хотите иметь возможность выполнять проверенное приведение.
Вместо того, чтобы делать всю эту ручную работу, мы можем автоматизировать генерацию всего этого кода с помощью препроцессора (с небольшой помощью из библиотеки Boost Preprocessor). Вот макрос, который генерирует определение перечисления вместе с функцией checked_enum_cast
. Это, вероятно, немного страшно (макросы генерации кода часто ужасны), но это очень полезный метод для ознакомления.
#include <stdexcept>
#include <boost/preprocessor.hpp>
// Internal helper to provide partial specialization for checked_enum_cast
template <typename Target, typename Source>
struct checked_enum_cast_impl;
// Exception thrown by checked_enum_cast on cast failure
struct invalid_enum_cast : std::out_of_range
{
invalid_enum_cast(const char* s)
: std::out_of_range(s) { }
};
// Checked cast function
template <typename Target, typename Source>
Target checked_enum_cast(Source s)
{
return checked_enum_cast_impl<Target, Source>::do_cast(s);
}
// Internal helper to help declare case labels in the checked cast function
#define X_DEFINE_SAFE_CAST_CASE(r, data, elem) case elem:
// Macro to define an enum with a checked cast function. name is the name of
// the enumeration to be defined and enumerators is the preprocessing sequence
// of enumerators to be defined. See the usage example below.
#define DEFINE_SAFE_CAST_ENUM(name, enumerators) \
enum name \
{ \
BOOST_PP_SEQ_ENUM(enumerators) \
}; \
\
template <typename Source> \
struct checked_enum_cast_impl<name, Source> \
{ \
static name do_cast(Source s) \
{ \
switch (s) \
{ \
BOOST_PP_SEQ_FOR_EACH(X_DEFINE_SAFE_CAST_CASE, 0, enumerators) \
return static_cast<name>(s); \
default: \
throw invalid_enum_cast(BOOST_PP_STRINGIZE(name)); \
} \
return name(); \
} \
};
Вот как вы могли бы использовать это с вашим примером CardColor
:
DEFINE_SAFE_CAST_ENUM(CardColor, (HEARTS) (CLUBS) (SPADES) (DIAMONDS))
int main()
{
checked_enum_cast<CardColor>(1); // ok
checked_enum_cast<CardColor>(400); // o noez! an exception!
}
Первая строка заменяет ваше определение enum CardColor ...
; он определяет перечисление и предоставляет специализацию, которая позволяет использовать checked_enum_cast
для приведения целых чисел к CardColor
.
Это может показаться большим хлопотом только для того, чтобы получить проверенную функцию приведения для ваших перечислений, но эта техника очень полезна и расширяема. Вы можете добавить функции, которые делают все виды вещей. Например, у меня есть одна, которая генерирует функции для преобразования перечислимых типов в и из строковых представлений и функции, которые выполняют несколько других преобразований и проверок, которые я использую для большинства моих перечислений.
Помните, вы должны написать и отладить этот большой, некрасивый макрос только один раз, тогда вы сможете использовать его везде.