Enum vs Strongly typed enum
C++C++11EnumsC++ Problem Overview
I am a beginner in C++ programming.
Today I come across a new topic: strongly typed enum
. I've researched it a bit but till now I am unable to find out why do we need this and what is the use of the same?
For example if we have:
enum xyz{a, b, c};
/*a = 0, b = 1, c = 2, (Typical C format)*/
Why do we need to write:
enum class xyz{a, b, c};
What are we trying to do here? My most important doubt is how to use it. Could you provide a small example, which will make me understand.
C++ Solutions
Solution 1 - C++
OK, first example: old-style enums do not have their own scope:
enum Animals {Bear, Cat, Chicken};
enum Birds {Eagle, Duck, Chicken}; // error! Chicken has already been declared!
enum class Fruits { Apple, Pear, Orange };
enum class Colours { Blue, White, Orange }; // no problem!
Second, they implicitly convert to integral types, which can lead to strange behaviour:
bool b = Bear && Duck; // what?
Finally, you can specify the underlying integral type of C++11 enums:
enum class Foo : char { A, B, C};
Previously, the underlying type was not specified, which could cause compatibility problems between platforms. Edit It has been pointed out in comments that you can also specify the underlying integral type of an "old style" enum in C++11.
Solution 2 - C++
There's a good article about enums at this IBM page, it's very detailed and well-written. Here are some important points in a nutshell:
The scoped enums solve most of the limitations incurred by regular enums: complete type safety, well-defined underlying type, scope issues, and forward declaration.
-
You get type safety by disallowing all implicit conversions of scoped enums to other types.
-
You get a new scope, and the enum is not anymore in the enclosing scope, saving itself from name conflicts.
-
Scoped enums gives you the ability to specify the underlying type of the enumeration, and for scoped enums, it defaults to int if you choose not to specify it.
-
Any enum with a fixed underlying type can be forward declared.
Solution 3 - C++
Values of enum class
is really of type enum class
, not underlying_type
as for C-enums.
enum xyz { a, b, c};
enum class xyz_c { d, f, e };
void f(xyz x)
{
}
void f_c(xyz_c x)
{
}
// OK.
f(0);
// OK for C++03 and C++11.
f(a);
// OK with C++11.
f(xyz::a);
// ERROR.
f_c(0);
// OK.
f_c(xyz_c::d);
Solution 4 - C++
The enum classes ("new enums", "strong enums") address three problems with traditional C++ enumerations:
- conventional
enums
implicitly convert toint
, causing errors when someone does not want an enumeration to act as an integer. - conventional
enums
export their enumerators to the surrounding scope, causing name clashes. - The underlying type of an
enum
cannot be specified, causing confusion, compatibility problems, and makes forward declaration impossible.
enum class
("strong enums") are strongly typed and scoped:
enum Alert { green, yellow, orange, red }; // traditional enum
enum class Color { red, blue }; // scoped and strongly typed enum
// no export of enumerator names into enclosing scope
// no implicit conversion to int
enum class TrafficLight { red, yellow, green };
Alert a = 7; // error (as ever in C++)
Color c = 7; // error: no int->Color conversion
int a2 = red; // ok: Alert->int conversion
int a3 = Alert::red; // error in C++98; ok in C++11
int a4 = blue; // error: blue not in scope
int a5 = Color::blue; // error: not Color->int conversion
Color a6 = Color::blue; // ok
As shown, traditional enums work as usual, but you can now optionally qualify with the enum's name.
The new enums are "enum class" because they combine aspects of traditional enumerations (names values) with aspects of classes (scoped members and absence of conversions).
Being able to specify the underlying type allow simpler interoperability and guaranteed sizes of enumerations:
enum class Color : char { red, blue }; // compact representation
enum class TrafficLight { red, yellow, green }; // by default, the underlying type is int
enum E { E1 = 1, E2 = 2, Ebig = 0xFFFFFFF0U }; // how big is an E?
// (whatever the old rules say;
// i.e. "implementation defined")
enum EE : unsigned long { EE1 = 1, EE2 = 2, EEbig = 0xFFFFFFF0U }; // now we can be specific
It also enables forward declaration of enums:
enum class Color_code : char; // (forward) declaration
void foobar(Color_code* p); // use of forward declaration
// ...
enum class Color_code : char { red, yellow, green, blue }; // definition
The underlying type must be one of the signed or unsigned integer types; the default is int
.
In the standard library, enum
classes are used for:
- Mapping systems specific error codes: In
<system_error>
:enum class errc
; - Pointer safety indicators: In
<memory>
:enum class pointer_safety { relaxed, preferred, strict };
- I/O stream errors: In
<iosfwd>
:enum class io_errc { stream = 1 };
- Asynchronous communications error handling: In
<future>
:enum class future_errc { broken_promise, future_already_retrieved, promise_already_satisfied };
Several of these have operators, such as ==
defined.
Solution 5 - C++
Enum Scope
Enumerations export their enumerators to the surrounding scope. This has two drawbacks. First, it can lead to name clashes, if two enumerators in different enums declared in the same scope have the same name; second, it's not possible to use an enumerator with a fully qualified name, including the enum name.
enum ESet {a0, a, a1, b1, c3};
enum EAlpha{a, b, c}
select = ESet::a; // error
select = a; // is ambigious