Reputation: 73
If I have a enum class
like this:
enum class Weekday {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
and I have a conversion from int
to Weekday
:
Weekday day = static_cast<int>(value);
To check that the value provided for conversion is valid, I should have the code:
if (value != 0 && value != 1 && value !=2 && value !=3 && value != 4 && value != 5 && value != 6) {
do_conversion(value);
}
It's so ugly. Is there any good way to do this?
Upvotes: 1
Views: 237
Reputation: 33931
This is probably over-complicating things on the surface, but it generalizes nicely and the template voodoo can be hidden in a header away from sight.
#include <stdexcept>
template <class TYPE>
TYPE do_conversion(int value)
{
if (value >= static_cast<int>(TYPE::First) &&
value < static_cast<int>(TYPE::Last))
{
return static_cast<TYPE>(value);
}
throw std::out_of_range("Inv value");
}
All of the conversion is moved to a function to keep the noise down.
The valid range is abstracted with extra enumerated values First and Last
. If the given value is in the range (First
, Last
], the value is assigned. If not, throw exception. Exception might not be the right thing to do depending on the frequency of invalid inputs, hardly the exception if bad input is common, but for here it helps keep the example simple.
Now invoking is a simple matter of
enum class Weekday
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
Last, // added
First = Monday // added
};
and later
Weekday day = do_conversion<Weekday>(value);
All of the noise from the conversion is out of the way of whatever needed the conversion so whoever is reading or writing that bit of code can get on with their job.
But what if you use it on an enum
without First
and Last
? The compiler messages can get bizarre, so it's a good idea to help the user out a bit. The following has been looted from How to detect whether there is a specific member variable in class? to make this a bit easier to use. Definitely take the time to read the answer to grok what's going on. I haven't come up with a decent way to combine HasFirst
and HasLast
. Please drop a comment and let me know if you have one.
#include <stdexcept>
#include <type_traits>
template <typename T, typename = int>
struct HasFirst : std::false_type { };
template <typename T>
struct HasFirst <T, decltype((void) T::First, 0)> : std::true_type { };
template <typename T, typename = int>
struct HasLast : std::false_type { };
template <typename T>
struct HasLast <T, decltype((void) T::Last, 0)> : std::true_type { };
template <class TYPE>
TYPE do_conversion(int value)
{
static_assert(HasFirst<TYPE>::value, "enum missing First");
static_assert(HasLast<TYPE>::value, "enum missing Last");
if (value >= static_cast<int>(TYPE::First) &&
value < static_cast<int>(TYPE::Last))
{
return static_cast<TYPE>(value);
}
throw std::out_of_range("Inv value");
}
Now if the enum
is missing the all-important meta values, the compiler can tell them with a simple error message.
Upvotes: 1
Reputation: 6474
The default underlying type of an enum class is integer. So to check whether an integer falls within the range of acceptable enum values you can compare it against the bounds of acceptable enum values using static_cast<int>
:
if (value >= static_cast<int>(Weekday::Monday) && value <= static_cast<int>(Weekday::Sunday))
{
do_conversion(value);
}
Upvotes: 1