Closer to Perfection: Get to Know C++11 Scoped and Based Enum Types

C  11 perfection

C++ enum types pack a set of related constants in an intuitive and efficient user-defined type. Can you ask for more? With two new C++11 enhancements, namely scoped enums and based enums, the answer is “yes.” Find out all about the recent facelift that C++11 enums underwent and learn how to refactor your code to benefit from the new enum features – without sacrificing performance or backward compatibility.

Enums are one of my favorite C++ features. They exemplify the notion of an efficient user-defined type without the heavy machinery of virtual functions, constructors, etc. (Compare C++ enums to other programming languages that still insist on using a full-blown class instead, and you’ll see what I mean.)

Yet, traditional enum types aren’t flawless. Throughout the past three decades, some of their limitations have irritated programmers and compilers alike. Three of the most frequently cited drawbacks of traditional enums are:

    • Enumerator names are visible from their enum’s enclosing scope.
    • You cannot override the default underlying type of an enum programmatically.
    • Enumerators convert to int automatically, no questions asked.

 

C++11 addresses these issues with revamped enumerations that give you tighter control over the scope, size, and implicit conversions of enum types. Let’s look at these new features more closely, and examine how they can improve both our code quality and frustration level.

A Matter of Scope

Programmers often discover the hard way that enumerators in the same scope must all be distinct from each other and from other variable names. Otherwise, they might clash, as the following example shows:

enum Color {

Bronze,

Silver,

Gold

};

enum Bullion

{

Silver, //conflicts with Color’s Silver

Gold, //conflicts with Color’s Gold Platinum

};

The enumerators Silver and Gold belong to different enum types and have different values. However, they clash because they’re visible from their enclosing scope (unlike, say, members of a class). The common workaround, namely appending the enum name to every enumerator, evokes memories from the pre-namespace era:

enum Color

{

BronzeColor,

SilverColor,

GoldColor

};

enum Bullion

{

SilverBullion,

GoldBullion,

PlatinumBullion

};

However, cumbersome names aren’t to everyone’s taste. Besides, name conflicts could still occur if you use third-party code.

Declaring enum types in namespaces offers only a partial solution to the problem. A typical project often declares all of its constants and enum types under the same namespace; and frankly, who uses namespaces these days anyway?

Enter Scoped Enums

C++11 solves the scoping problem with a new category called scoped enums. A scoped enum looks exactly as a traditional enum except that the keyword class (or struct – the two keywords are interchangeable in this context) appears between the keyword enum and the enum name, as shown in the following example:

enum class  Color //C++11 scoped enum

{

Bronze,

Silver,

Gold

};

enum struct Bullion //C++11 scoped enum

{

Silver, //doesn’t conflict with Color’s Silver

Gold, //ditto

Platinum

};

Enumerators of a scoped enum require a qualified name when referred to from an enclosing scope:

Color col1=Bronze; //error, Bronze not in scope

Color col2=Color::Bronze; //OK

if ((col2==Color::Silver) || (col2==Color::Gold))//OK

//…

Strong Typing

Traditional enumerators aren’t strongly typed. They automatically convert to int:

int  cointype= BronzeColor; //OK, unscoped enum

As opposed to their unscoped counterparts, scoped enums are strongly-typed. Implicit conversions to int (or any other integral types) are not permitted. To convert a scoped enumerator to int, you have to use an explicit cast:

int carcolor=Color::Silver; //error, implicit conversion to int not allowed

int carcolor=static_cast<int>(Color::Silver); //OK

One Size Doesn’t Fit All

All enum types are implemented as a built-in integral type known as the underlying type. In C, the underlying type is always int. In C++03, the underlying type is implementation-defined. An implementation is free to choose an arbitrary integral type (char, short, int, long, and their unsigned counterparts) as an enum’s underlying type. Furthermore, a C++ implementation is allowed to assign a different underlying type to different enum types. The only requirements in the C++03 standard are that the underlying type is an integral type that can represent all the enumerator values defined in the enumeration, and that the underlying type is not larger than int, unless the value of an enumerator cannot fit in an int or unsigned int.

Thus, a typical C++03 implementation may assign different underlying types to the following enum types:

//C++03

enum Bool {False, True}; // fits into char, signed char, unsigned char

enum RecSize

{DefaultSize=1000000, LargeSize=2000000}; //underlying type is int

Of course, an implementation may adhere to int as the sole underlying type regardless of the values of the enumerators. From my experience, that’s what the majority of C++03 compilers in fact do.

The under-specification of an enum’s underlying type in C++03 can be a problem if you need platform-independent sizes, e.g., when sending data across an HTTP connection, or when the data has to be stored compactly. In C++03, there’s really nothing much that you can do to override the default underlying type – at least not in a standardized manner.

C++11, however, lets you specify the underlying type of an enum explicitly by using an enum base. An enum base declaration looks syntactically similar to a base class in a derived class declaration:

enum Bool: char {False, True}; //C++11 based enum

In the example above, the programmer specified char as the underlying type of Bool. Consequently, every enumerator of Bool occupies only one byte of memory. If the value of an enumerator cannot be represented by the underlying type, a compilation error occurs.

Selecting char as the underlying type of an enum type ensures among the rest that enumerators can be transmitted via HTTP without the onerous little-endian versus big endian byte reordering. Additionally, the compact size of one byte (instead of four) per enumerator implies that a large number of enum values can be stored efficiently in a smartphone’s memory or a data file.

Of course, this doesn’t mean that you should rush to specify char as the underlying type of your enum types – quite the contrary. As a rule, let your compiler assign the default underlying type unless you have a good reason to override it. Usually, the default underlying type is also the most efficient datatype for the target hardware.

Combining Features

Based enums aren’t scoped by default. They simply have a fixed, user-specified underlying type. If you want the benefits of both scoped enums and based enums, combine the two features, like this:

enum class  Bool: char {False, True}; //C++11 scoped and based enum

int x=sizeof (Bool); //x=1

int  y =static_cast<int> (Bool::False); //y=0

++y;

Of course, traditional enums are still available in C++11 with the same semantics and scoping rules as before. Therefore, legacy code will not break when you upgrade your compiler. For example, a traditional enumerator will still convert to int implicitly even in C++11:

enum Direction (Up, Down};

int dir=Down; //OK in C++11 as well

Recall that you may use qualified enumerator names even with traditional enums (with scoped enums, you’re obliged to use qualified names exclusively):

int dir=Direction::Down; //OK, unscoped enum

However, it’s advisable to go through existing C++ code and refactor it, possibly replacing legacy enums with scoped enums; this shouldn’t affect their underlying type. Likewise, if you need a fixed, platform-independent underlying type, add an enum base to your enum declarations.

In Conclusion

C++11 offers two new categories of enum types: scoped enums and based enums. Enumerators of a scoped enum require a qualified name such as enumname::enumerator when you refer to them from an enclosing scope. In addition, scoped enums are strongly-typed, so you must use an explicit cast to convert them to int if necessary. A based enum lets you specify its underlying type programmatically. With respect to compiler support, GCC 4.4, Intel’s C++ 12, MSVC 11, IBM’s XLC++ 12.1, Clang 2.9, Embarcadero C++ Builder, and others support the new C++11 enum features.

Danny Kalev is a certified system analyst and software engineer specializing in C++. Kalev has written several C++ textbooks and contributes C++ content regularly on various software developers’ sites. He was a member of the C++ standards committee and has a master’s degree in general linguistics.

See also:

cta-jeff-sutherland-webinarod2
subscribe-1
  • http://twitter.com/tomkirbygreen Tom Kirby-Green

    Nice article. Assuming it wasn’t said in jest, I’d be interested to hear more of your thinking behind the statement ‘no one uses namespaces these days’. 
    Kind regards – Tom

  • Catherine

    That’s not just controversial, I think that’s just wrong.  
    I don’t know any major C++ libraries that do not use namespaces (yeah Qt but that’s because Qt was a pre-standard library). 
     
    And I don’t know how you can say this about namespaces : “it’s so difficult to get them right without messing up things and wasting time”. Are we talking about the same thing ? Sure, namespaces make you type more (not that much in a modern IDE), but difficult to get them right… !!!! ???

  • ErikWB

    As Catherine, I don’t really see the problem with using namespaces.  
     
    Sure, if you get tired of writing myNamespace:: everywhere, you can do the “using namespace myNamespace” in your cpp file, but by using namespaces in the headers you get rid of the problem of name clashes. 
     
    As a better alternative to using an entire namespace, I’d suggest doing “using std::cerr;” for the functions actually used.  
     
    Also, if you do either of these inside the functions where you need it, it reduces the chances of clashes even further.

  • John

    Just an addition that another nice new feature made possible by base types is forward declaring enums. This has the benefit that functions can happily pass around enum values without including huge sets of enum constants. Only the the actual users of the values need to include the values.

  • Danny Kalev

    Re: “no one uses namespaces these days”: it’s a bit controversial, I agree. However, my experience in recent years suggests that projects and textbook authors alike are running away from namespaces. For example, a very popular book that I tech edited a couple of months ago finally ditched qualified names such as std::string and std::scout in favor of plain string and cout in its latest edition. Both the authors and the editors came to a consensus about that. Similarly, I know quite a few C++ projects that began as namespace-organized and recently decided to remove the namespaces because they merely get in the way.  
    The truth is that namespaces don’t live up to their expectations and the fact that the majority of C++ programmers these use bypass them with a  
    using namespace std;  
    directive suggests that they are more of a nuisance than a real solution. I’m biased about this of course, but I count namespaces, along with exceptions and exception specification in particular as the C++ features from which you should steer clear — it’s so difficult to get them right without messing up things and wasting time. At least as far as exception specifications are concerned, the committee came to the same conclusion too: they were removed from C++11.  
    Danny

  • Dilip

    Is there a RSS feed to just your posts?

  • Prashant Kaw
  • Seth

    IME things are moving the opposite direction on namespaces; using namespace std is frowned on more and only poorly behaved libraries put their symbols in the global namespace. Even std is adding more namespaces such as chrono. 
     
    Anyway, what I really wanted to comment on was: “Recall that you may use qualified enumerator names even with traditional enums,” 
     
    Since there’s no previous mention of this in the article it sounds like you’re saying “Recall from C++03 that…” In C++03 it is actually not permitted to qualify enumerators with the enum name. However some compilers permitted this as an extension. This feature only became standardized in C++11 so this should really be called out as a new feature.

  • Danny Kalev

    Thanks for the clarification Seth. I was under the impression that qualified enumerators were available in C++03 (yes, mostly because a few C++03 compilers support this feature) but apparently, this isn’t the case: qualified (traditional) enumerators are a C++11 feature too. Well, at least I covered in the post!