Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting if casting an int to an enum results into a non-enumerated value

Tags:

c++

enums

Let's say I have something like this :

enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES};

CardColor MyColor = static_cast<CardColor>(100);

Is there a (simple) way to detect, either at compile-time or at runtime, that the value of MyColor doesn't correspond to any enumerated value ?

And more generally, if the enum values are not following each other, for instance :

enum CardColor { HEARTS = 0, DIAMONDS, CLUBS = 4, SPADES};
like image 791
Jérôme Avatar asked Jan 25 '11 16:01

Jérôme


People also ask

Can you cast an int to an enum?

You can explicitly type cast an int to a particular enum type, as shown below.

Can enum have int values?

The enum can be of any numeric data type such as byte, sbyte, short, ushort, int, uint, long, or ulong. However, an enum cannot be a string type.

Can you cast an enum?

Enum's in . Net are integral types and therefore any valid integral value can be cast to an Enum type. This is still possible even when the value being cast is outside of the values defined for the given enumeration!


3 Answers

clang have support for dynamic overflow checks. See -fsanitize=enum switch. A program compiled with this switch will signal enum assignment errors through stderr output. This will allow you to do debug tests. It is not suitable to test suspicious input in official build.

like image 110
ZAB Avatar answered Sep 17 '22 14:09

ZAB


Simplest run-time solution would be to not use static_cast but use a function that does the check for you. If you put your enum inside a class you can do that through the class. Something like:

class CardCheck
{
public:
  enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES };

  explicit CardCheck( int x ) : c( static_cast< CardColor >( x ) )
  {
     switch( c )
     {
       case HEARTS: case DIAMONDS: case CLUBS: case SPADES:
          break;

       default:
         // assert or throw
    }
  }

  CardColor get() const
  {
     return c;
  }

 private:
  CardColor c;      
};
like image 9
CashCow Avatar answered Oct 23 '22 18:10

CashCow


CashCow presents a decent answer to this question: it's certainly straightforward to write a custom function to perform a checked cast.

Unfortunately, it's also a lot of work and you must make sure to keep it synchronized with the enumeration so that the list of enumerators in the enumeration definition is the same as the list of enumerators in the checked cast function. You also have to write one of these for each enumeration to which you want to be able to perform a checked cast.

Instead of doing all this manual work, we can automate generation of all of this code using the preprocessor (with a little help from the Boost Preprocessor library). Here is a macro that generates an enumeration definition along with a checked_enum_cast function. It's probably a bit scary looking (code generation macros are often horrible to look upon), but it's an extremely useful technique to become familiar with.

#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();                                                 \
        }                                                                  \
    };

Here is how you would use that with your CardColor example:

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!
}

The first line replaces your enum CardColor ... definition; it defines the enumeration and provides a specialization that allows you to use checked_enum_cast to cast integers to CardColor.

This may look like a lot of hassle just to get a checked cast function for your enums, but this technique is very useful and extensible. You can add functions that do all sorts of things. For example, I have one that generates functions to convert enumerated types to and from string representations and functions that perform several other conversions and checks that I use for most of my enumerations.

Remember, you have to write and debug that big, ugly macro just once, then you can use it everywhere.

like image 16
James McNellis Avatar answered Oct 23 '22 18:10

James McNellis