Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safe way to convert an integer in an enum

Tags:

c++

c++11

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?

like image 783
Elvis Dukaj Avatar asked Jul 23 '13 13:07

Elvis Dukaj


People also ask

How do I convert an int to enum?

Use the Type Casting to Convert an Int to Enum in C# The correct syntax to use type casting is as follows. Copy YourEnum variableName = (YourEnum)yourInt; The program below shows how we can use the type casting to cast an int to enum in C#. We have cast our integer value to enum constant One .

Can we give integer in enum?

No, we can have only strings as elements in an enumeration.

Can you typecast an enum?

Yes. In C enum types are just int s under the covers. Typecast them to whatever you want. enums are not always ints in C.

Can you change the value of an enum in C?

You can change default values of enum elements during declaration (if necessary).


2 Answers

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.

like image 91
Cassio Neri Avatar answered Sep 17 '22 13:09

Cassio Neri


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;
    }
}
like image 35
Vaughn Cato Avatar answered Sep 21 '22 13:09

Vaughn Cato