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.
To get the value of enum we can simply typecast it to its type. In the first example, the default type is int so we have to typecast it to int. Also, we can get the string value of that enum by using the ToString() method as below.
In C programming, an enumeration type (also called enum) is a data type that consists of integral constants. To define enums, the enum keyword is used. enum flag {const1, const2, ..., constN}; By default, const1 is 0, const2 is 1 and so on.
The strongly typed enum pattern or the type-safe enum pattern as it is called, can be used to mitigate the design and usage constraints we discussed in the earlier section when working with enums. This pattern makes sure that the type is extensible and you can use it much like an enum.
You should use enum types any time you need to represent a fixed set of constants. That includes natural enum types such as the planets in our solar system and data sets where you know all possible values at compile time—for example, the choices on a menu, command line flags, and so on.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With