Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
218 views
in Technique[技术] by (71.8m points)

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

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};
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...