14

What happens if I cast an integer into an enum class, but the value is not present in the enum? For example: I want a function that tests if an integer has some value from an enum class:

enum class EnumClass { A, B = 4, C = 9, D = 60 };

bool checkEnumClass( int v )
{
    switch( static_cast< EnumClass >( v ) )
    {
    case EnumClass::A:
    case EnumClass::B:
    case EnumClass::C:
    case EnumClass::D:
        return true;
    default:
        return false;
    }
}

checkEnumClass( 0 ) == true;
checkEnumClass( 7 ) == false;   // is this true?

Is this the right way to check if an integer is convertible to an enum?

Casey
  • 41,449
  • 7
  • 95
  • 125
Elvis Dukaj
  • 7,142
  • 12
  • 43
  • 85
  • why not use checkEnumClass(EnumClass )? – camino Jul 23 '13 at 13:25
  • Because I've to parse some integer... and check (or better assure) that the integer is convertable into enum – Elvis Dukaj Jul 23 '13 at 13:26
  • An enum can hold any value between its smallest and largest values: http://stackoverflow.com/a/7676940/951890 – Vaughn Cato Jul 23 '13 at 13:33
  • enum class are not convertable implicity in integer (like enum) – Elvis Dukaj Jul 23 '13 at 13:35
  • @elvis.dukaj: oops -- wasn't thinking – Vaughn Cato Jul 23 '13 at 13:41
  • 1
    from the c++11 draft: A value of integral or enumeration type can be explicitly converted to an enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2). Otherwise, the resulting value is unspecified (and might not be in that range). So maybe here it is better to use another data structure instead of enum – camino Jul 23 '13 at 13:45

4 Answers4

15

I don't see any fundamentally better solution than the one offered by the OP. However, it has a small defect for which I can suggest a (non-standard) workaround.

The issue is the following. Suppose the code today is as in the OP but, one day, someone adds a new enumerator to EnumClass which becomes:

enum class EnumClass { A, B = 4, C = 9, D = 60, E = 70 };

Suppose also that this person forgets to update the definition of checkEnumClass (which isn't unlikely to happen, especially if the code is in in another file). Then,

checkEnumClass( 70 );

will return false despite the fact that 70 is now a valid value. Unit tests might help catch this bug but the person must remember to update the test. (Recall that they forgot to update the code at the first place!)

Unfortunately, standard C++ doesn't offer a way to force a switch on an enum to cover all the cases (unlike D which offers the final switch statement).

However, there are compiler-specific features that can do this for you.

For GCC (and, I believe, Clang, as well) you can add the compiler option -Wswitch (or -Wall which implies -Wswitch). For Visual Studio you can add

#pragma warning(error : 4062)

to the file containing checkEnumClass (not the file containing the enum definition)

Finally, you must slightly change checkEnumClass because a default label tells the compiler that all cases are covered. The code should be like this:

bool checkEnumClass( int v )
{
    switch( static_cast< EnumClass >( v ) )
    {
    case EnumClass::A:
    case EnumClass::B:
    case EnumClass::C:
    case EnumClass::D:
        return true;
    }
    return false;
}

With this workaround, the person who included the enumerator E but forgot to update checkEnumClass accordingly will get the following error/warning:

GCC:

warning: enumeration value 'E' not handled in switch [-Wswitch]

Visual Studio:

error C4062: enumerator 'E' in switch of enum 'EnumClass' is not handled
switch( static_cast< EnumClass >( v ) )

Update 1: Following the comment by elvis.dukaj.

As a good practice add -Werror to GCC's options to turn all warnings into errors.

Update 2: Better than -Wswitch is -Wswitch-enum which will raise the warning (or error if -Werror) even when there's a default label. Unfortunately I don't know any similar feature in Visual Studio.

Community
  • 1
  • 1
Cassio Neri
  • 19,583
  • 7
  • 46
  • 68
  • I always is -Wall -Eerror so I enable all warnings and traits them as errors (lots of stupid error avoided in this way). – Elvis Dukaj Jul 23 '13 at 14:48
  • @elvis.dukaj: This is good practice (which I also follow) but for this particular issue (a `switch` on an `enum` which doesn't cover all enumerators) it's crucial that there is no `default` label otherwise the `-Wswitch` will not help :-( – Cassio Neri Jul 23 '13 at 14:55
  • @elvis.dukaj: Actually, I found a way (and updated the answer accordingly) to make the `default` label not prevent the warning/error. Thanks for your remarks. – Cassio Neri Jul 23 '13 at 15:06
  • All modern compiler tell if a value is not covered in switch (if default is not present) so I like this method. – Elvis Dukaj Jul 23 '13 at 15:10
  • No need to disable warnings, just use `__builtin_unreachable()` in the default statements for gcc/clang and `__assume(0)` for msvc, – Zaffy Dec 13 '19 at 15:50
7

An enum can hold any value between its smallest and largest values, so what you have is mainly correct. The only thing you need to do additionally is to make sure the integer argument is in the proper range, since if you try to cast an int that is outside the range of the enumeration, you have undefined behavior:

bool checkEnumClass( int v )
{
    if (v < static_cast<int>(EnumClass::A)) return false;
    if (v > static_cast<int>(EnumClass::D)) return false;

    switch( static_cast< EnumClass >( v ) )
    {
    case EnumClass::A:
    case EnumClass::B:
    case EnumClass::C:
    case EnumClass::D:
        return true;
    default:
        return false;
    }
}
Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • Won't the range check be caught by the default in the switch? Or am I missing something? – firebush Dec 08 '15 at 22:34
  • 2
    It isn't legal to cast an int that is outside the range of enumerated values, so that has to be checked first. – Vaughn Cato Dec 08 '15 at 23:47
  • I see. If you try to cast to EnumClass from an integral value outside of the min and max of the enumeration, then the result is undefined behavior. See the first few paragraphs [here](http://stackoverflow.com/a/18195408/629530). Thanks for the answer. – firebush Dec 10 '15 at 14:37
  • regarding "An enum can hold any value between its smallest and largest values,": this is true; however in most situations the actual range of the enum is wider than this range, i.e. some values outside of the smallest-largest range can also be stored. – M.M Dec 10 '15 at 20:15
  • 1
    For an `enum class`, the entire range of the underlying type is valid; that type is `int` if not otherwise specified. For C-style enums, the range is (roughly speaking) any value that can be represented in the minimum number of bits required to store the actual enumerators. – M.M Dec 10 '15 at 20:17
5

If you ever need compile-time checking of the enum value, you could try this:

template <int I> struct check_enum { static const bool value = false; };

template <> struct check_enum<static_cast<int>(EnumClass::A)>
{ static const bool value = true; };

template <> struct check_enum<static_cast<int>(EnumClass::B)>
{ static const bool value = true; };

template <> struct check_enum<static_cast<int>(EnumClass::C)>
{ static const bool value = true; };

template <> struct check_enum<static_cast<int>(EnumClass::D)>
{ static const bool value = true; };

Then, you can use it this way:

static_assert(check_enum<0>::value, "invalid enum value"); // ok!
static_assert(check_enum<1>::value, "invalid enum value"); // compile error

Live demo.

Edit: The same approach is possible with C++14 template variables.

template <int I> constexpr bool check_enum = false;
template <> constexpr bool check_enum<static_cast<int>(EnumClass::A)> = true;
template <> constexpr bool check_enum<static_cast<int>(EnumClass::B)> = true;
template <> constexpr bool check_enum<static_cast<int>(EnumClass::C)> = true;
template <> constexpr bool check_enum<static_cast<int>(EnumClass::D)> = true;

static_assert(check_enum<0>, "invalid enum value"); // ok!
static_assert(check_enum<1>, "invalid enum value"); // compile error

The main disadvantage of those approaches is the effort of specializing every value, you must think if the effort is worthwhile. And if some value is missed, then could be difficult to find and fix the problem.

PaperBirdMaster
  • 12,806
  • 9
  • 48
  • 94
  • If there are so many enum values, then this approach won't be practical – iammilind Jul 23 '13 at 14:26
  • @iammilind : like i said: *The main disadvantage of this approach is the code bloat and the extra effort of specialize the template for each valid value* – PaperBirdMaster Jul 23 '13 at 14:36
  • There is no code bloat in your approach. Everything is optimized after compilation. The only problem is too much of effort for specializing every value which is not worth the effort. If some value is missed, then it's difficult fix the problem. – iammilind Jul 23 '13 at 15:30
  • Maybe i don't explain myself good due to my lack of english skills, i've changed the paragraph about the disadvantages with your words @iammilind – PaperBirdMaster Jul 23 '13 at 15:49
-3

Just check that the int is not greater than the max possible value in your check class, no need for a switch statement, just use an if statement, or better yet, just a bool.

bool checkEnumClass(int i)
{
    return (i <= 7);
}
drz
  • 962
  • 7
  • 22
  • 1
    I have edited the question. If the enum has some "holes" it's more I cannot just say i <= n – Elvis Dukaj Jul 23 '13 at 13:24
  • Why would you want your enum to contain holes? Typically if you are representing a list of values (such as status codes, etc.) then you want to just use constants instead. – drz Jul 23 '13 at 13:26
  • 3
    The enum rappresent some constant value that aren't contigue. It's not my choise have this kind of enum – Elvis Dukaj Jul 23 '13 at 13:27
  • This is a bad approach. The first two lines of code of @VaughnCato answer covers the min and max values of the enum dynamically. In your code when the enum change the check is broken – Yitzchak Jul 11 '18 at 11:27