views:

3896

answers:

9

It is common knowledge that built-in enums in C++ are not typesafe. I was wondering which classes implementing typesafe enums are used out there... I myself use the following "bicycle", but it is somewhat verbose and limited:

typesafeenum.h:

struct TypesafeEnum
{
// Construction:
public:
    TypesafeEnum(): id (next_id++), name("") {}
    TypesafeEnum(const std::string& n): id(next_id++), name(n) {}

// Operations:
public:
    bool operator == (const TypesafeEnum& right) const;
    bool operator != (const TypesafeEnum& right) const;
    bool operator < (const TypesafeEnum& right) const;

    std::string to_string() const { return name; }

// Implementation:
private:
    static int next_id;
    int id;
    std::string name;
};

typesafeenum.cpp:

int TypesafeEnum::next_id = 1;

bool TypesafeEnum::operator== (const TypesafeEnum& right) const 
{ return id == right.id; }

bool TypesafeEnum::operator!= (const TypesafeEnum& right) const 
{ return !operator== (right); }

bool TypesafeEnum::operator< (const TypesafeEnum& right) const  
{ return id < right.id; }

Usage:

class Dialog 
{
 ...
    struct Result: public TypesafeEnum
    {
        static const Result CANCEL("Cancel");
        static const Result OK("Ok");
    };


    Result doModal();
 ...
};

const Dialog::Result Dialog::Result::OK;
const Dialog::Result Dialog::Result::CANCEL;

Addition: I think I should have been more specific about the requirements. I'll try to summarize them:

Priority 1: Setting an enum variable to an invalid value should be impossible (a compile-time error) with no exceptions.

Priority 2: Converting an enum value to/from an int should be possible with a single explicit function/method call.

Priority 3: As compact, elegant and convenient declaration and usage as possible

Priority 4: Converting enum values to and from strings.

Priority 5: (Nice to have) Possibility to iterate over enum values.

+6  A: 

I don't. Way too much overhead for little benefit. Also, being able to caste enumerations to different data types for serialization is a very handy tool. I have never seen an instance where a "Type safe" enumeration would be worth the overhead and complexity where C++ offers a good enough implementation already.

nlaq
Don't shoot the messenger... Up voting again
fizzer
A type safe enum can be implemented using a template such that there is no runtime overhead whatsoever. And if you use say, BOOST_ENUM, all the complexity is in a library.
Joseph Garvin
A: 

I think the Java enum would be a good model to follow. Essentially, the Java form would look like this:

public enum Result {
    OK("OK"), CANCEL("Cancel");

    private final String name;

    Result(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

What's interesting about the Java approach is that OK and CANCEL are immutable, singleton instances of Result (with the methods that you see). You cannot create any further instances of Result. Since they're singletons, you can compare by pointer/reference---very handy. :-)

ETA: In Java, instead of doing bitmasks by hand, instead you use an EnumSet to specify a bit set (it implements the Set interface, and works like sets---but implemented using bitmasks). Much more readable than hand-written bitmask manipulation!

Chris Jester-Young
That's very nice, but how can we do this in c++?And I think using addresses for comparison is not very harmless, consider if enum values get serialized on disk? Each time we might get different ordering.
Alex Jenter
Remember, in Java, you can't do `<` comparisons on pointers, only `==` or `!=`. So as long as they're distinct, that's good enough to compare by pointer. :-) I'll see if I can write a C++ implementation sometime.
Chris Jester-Young
the java enum seems to be an instance of cope's curiously recurring pattern (http://en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern). naict by googleing, no one has tried this approach. i wonder if it is due to the difference between java generics and the stl semantics?
Ray Tayek
+14  A: 

A nice compromise method is this:

struct Flintstones {
   enum E {
      Fred,
      Barney,
      Wilma
   };
};

Flintstones::E fred = Flintstones::Fred;
Flintstones::E barney = Flintstones::Barney;

It's not typesafe in the same sense that your version is, but the usage is nicer than standard enums, and you can still take advantage of integer conversion when you need it.

Charlie
Very nice. Good work!
nlaq
Wouldn't this be cleaner with a namespace instead of a struct?
jmucchiello
Interesting thought. That would eliminate one occasional issue with this, which is having someone attempt to declare a Flintstones rather than a Flintstones::E.
Charlie
Follow-up on the namespace thing: for enums that live inside a class or struct, you can't use a namespace. If the enum *doesn't* live in a class or struct, I think you're right that using a namespace is cleaner.
Charlie
@Charlie: Another solution to that is to give the struct a private constructor, and it will work in the nested case.
Joseph Garvin
+1  A: 

I gave an answer to this here, on a different topic. It's a different style of approach which allows most of the same functionality without requiring modification to the original enum definition (and consequently allowing usage in cases where you don't define the enum). It also allows runtime range checking.

The downside of my approach is that it doesn't programmatically enforce the coupling between the enum and the helper class, so they have to be updated in parallel. It works for me, but YMMV.

Nick
+2  A: 

My take is that you're inventing a problem and then fitting a solution onto it. I see no need to do an elaborate framework for an enumeration of values. If you are dedicated to having your values only be members of a certain set, you could hack up a variant of a unique set datatype.

Paul Nathan
A: 

Here's my answer to a previous question, Should I use #define, enum or const?. Seems pretty germane.

Don Wakefield
+21  A: 

I'm currently playing around with the Boost.Enum proposal from the Boost Vault (filename enum_rev4.6.zip). Although it was never officially submitted for inclusion into Boost, it's useable as-is. (Documentation is lacking but is made up for by clear source code and good tests.)

Boost.Enum lets you declare an enum like this:

BOOST_ENUM_VALUES(Level, const char*,
    (Abort)("unrecoverable problem")
    (Error)("recoverable problem")
    (Alert)("unexpected behavior")
    (Info) ("expected behavior")
    (Trace)("normal flow of execution")
    (Debug)("detailed object state listings")
)

And have it automatically expand to this:

class Level : public boost::detail::enum_base<Level, string>
{
public:
    enum domain
    {
        Abort,
        Error,
        Alert,
        Info,
        Trace,
        Debug,
    };

    BOOST_STATIC_CONSTANT(index_type, size = 6);

    Level() {}
    Level(domain index) : boost::detail::enum_base<Level, string>(index) {}

    typedef boost::optional<Level> optional;
    static optional get_by_name(const char* str)
    {
        if(strcmp(str, "Abort") == 0) return optional(Abort);
        if(strcmp(str, "Error") == 0) return optional(Error);
        if(strcmp(str, "Alert") == 0) return optional(Alert);
        if(strcmp(str, "Info") == 0) return optional(Info);
        if(strcmp(str, "Trace") == 0) return optional(Trace);
        if(strcmp(str, "Debug") == 0) return optional(Debug);
        return optional();
    }

private:
    friend class boost::detail::enum_base<Level, string>;
    static const char* names(domain index)
    {
        switch(index)
        {
        case Abort: return "Abort";
        case Error: return "Error";
        case Alert: return "Alert";
        case Info: return "Info";
        case Trace: return "Trace";
        case Debug: return "Debug";
        default: return NULL;
        }
    }

    typedef boost::optional<value_type> optional_value;
    static optional_value values(domain index)
    {
        switch(index)
        {
        case Abort: return optional_value("unrecoverable problem");
        case Error: return optional_value("recoverable problem");
        case Alert: return optional_value("unexpected behavior");
        case Info: return optional_value("expected behavior");
        case Trace: return optional_value("normal flow of execution");
        case Debug: return optional_value("detailed object state listings");
        default: return optional_value();
        }
    }
};

It satisfies all five of the priorities which you list.

Josh Kelley
why is this needed to solve a problem that dosn't exist??!! *head explodes*
nlaq
No need for exploding heads; the complexity is nicely hidden behind macros and a clean interface. And standard C enums have several real problems: lack of type safety, no ability to query an enum type for its max size, lack of automatic conversion to/from strings.
Josh Kelley
This is what I was looking for, thanks! :)
triton
I originally designed boost::enum to be sort of a string table that was built at compile-time and could be used at run-time. Eventually it morphed into a more generic type-safe enum. It never got included with boost because of my lack of time to dedicate to documentation. Nice to see it out in the wild!
fried
This looks seriously useful, you should still try to get it in. Maybe a version that uses the new C++0x strongly typed enums (still nice to get the boost::optional interface and string conversion).
Joseph Garvin
Yeh I second that, I just downloaded and started using it successfully in under an hour, very useful :)
radman
+5  A: 

I use C++0x typesafe enums. I use some helper template/macros that provide the to/from string functionality.

enum class Result { Ok, Cancel};
Roddy
Which compilers support this?
Joachim Sauer
Why not ask stackoverflow? :-) http://stackoverflow.com/questions/934183/good-urls-for-tracking-implementation-progress-of-c0x-in-msvc-and-gcc
Roddy
@Roddy: so basically you’re using a feature that just **one** compiler supports, on up-to-date systems (as opposed to most systems, which do not have gcc 4.4 yet)?
Konrad Rudolph
@Konrad: No, I'm using one that at least two compilers support, (and I'm not using GCC 4.4). Code portability to other compilers is not an issue for me in my current projects.
Roddy
I'm pretty sure the latest MSVC will support it too. If it works on Linux (GCC), OS X (GCC), and Windows (MSVC), that's 'good enough' portability for a fair number of users. Obviously not for everyone though ;)
Joseph Garvin
A: 

Not sure if this post is too late, but there's an article on GameDev.net which satisfies all but the 5th point (ability to iterate over enumerators): http://www.gamedev.net/reference/snippets/features/cppstringizing/

The method described by the article allows string conversion support for existing enumerations without changing their code. If you only want support for new enumerations though, I'd go with Boost.Enum (mentioned above).