- C++98-style enums are now known as unscoped enums.
- Enumerators of scoped enums ae visible only within the enum. They convert to other types only with a cast.
- Both scoped and unscoped enums support specification of the underlying type. The default underlying type for scoped enums is int. Unscoped enums have no default underlying type.
- Scoped enums may always be forward-declared. Unscoped enums may be forward-declared only if their declaration specifies an underlying type.
Generally declaring a name inside culy bracer limits its scope. However, this doesn't apply to C++98-style enums.
// C++98-style enum
enum Color { black, white, red };
auto white = false; // error! white already declared in this scope
Their new C++11 counterpart scoped enums don't leak name in this way:
enum class Color { black, white, red }; // scoped inside Color
auto white = false; // fine, no problem
Color c = Color::white; // fine
auto r = Color::red; // also fine
Not only does scoped enums reduce namespace pollution, their enumerators are also much more strongly typed. Unscoped enumerators implicitly convert to integral types:
// dangerous if unware of
enum Color { black, white, red };
std::vector<std::size_t> primeFactors(std::size_t x);
Color c = red;
...
if (c < 14.5) { // compare Color to double !
auto factors = primeFactors(c);
}
The above code is dangerous if you are unaware of the underlying implicit conversion. Using scoped enum will help the compiler to detect and prevent from you doing that. If that's really what you want to do, you should write explicit conversion:
...
if (static_cast<double>(c) < 14.5) {
auto factors = primeFactors(static_cast<std::size_t>(c));
}
In addition, scoped enums has underlying default type of int, while unscoped enum doesn't have default type. It would need to infer from the detailed enumerator numbers to decide the best underlying type. This makes scoped enum easier to do forward declaration.
Both scoped and unscoped enums support specification of underlying type. This will make unscoped enum also forward-declarable:
enum class Status: std::uint32_t; // underlying type for Status is std::uint32_t
enum Color: std::uint8_t; // fwd decl for unscoped enum enabled
However, there is one place that unscoped enum might come handy. Suppose we have a tuple holding values for the name, email address and reputation:
using UserInfo = std::tuple<std::string, std::string, std::size_t>;
UserInfo uInfo;
...
auto email = std::get<1>(uInfo); // try grab the user email
However, it's tedious to remeber the corresponding index for each field. Later on in the code, you are very likely to get confused and write incorrect indexing. We could rely on implicit conversion of the unscoped enum:
enum UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo;
...
auto email = std::get<uiEmail>(uInfo);
The corresponding code with scoped enum is substantially more verbose:
enum class UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo;
...
auto email = std::get<static_cast<std::size_t>(UserInfoFields::uiEmail)>(uInfo);