Better types in C++11 - nullptr, enum classes (strongly typed enumerations) and cstdint


By Alex Allain

C++ has from the beginning attempted to improve on the type system of C, adding features like classes that let you build better types and enums, which eliminate the need for some uses of the preprocessor (which is not at all type safe). C++ also performs fewer implicit type conversions for you (such as not allowing implicit assignment from void*), letting the compiler find more bugs for you.

C++11 goes even further. Even though enums got rid of the need for integer #define constants, we still had the ugly, poorly typed NULL pointer. C++11 cleans this up by adding an explicit, clear nullptr value with its own type. C++11 also brings new, strongly typed enums. In this article, I'll cover both these improvements.

Why do we need strongly typed enums?

So why do we need strongly typed enums anyway? Old-style C++ enums are essentially integers; they could be compared with integers or with other enums of different types. The thing is, you normally don't want to do that since enums are supposed to be some fixed list of enumerated values. Why would you want to compare to some other enum type (or an integer)? It's like saying, "please compare this kind of nail with this kind of toothbrush." It makes no sense, and you probably don't mean to do it. But old-style C++ enums will happily tell you, "why yes, this nail isn't like this toothbrush" or, worse, they might compare equal because they happen to share the same underlying integer value ("ah yes, this nail IS a Panasonic electronic toothbrush"). Now, with strongly typed enums, the compiler will tell you that you're doing it. If you really mean it, you can always use a typecast.

Another limitation is that enum values were unscoped--in other words, you couldn't have two enumerations that shared the same name:

// this code won't compile!
enum Color {RED, GREEN, BLUE}; 
enum Feelings {EXCITED, MOODY, BLUE};

Strongly typed enums - enum classes

Enter strongly typed enums--and I don't mean enums. Strongly typed enums are a new kind of enum, declared like so:

// this code will compile (if your compiler supports C++11 strongly typed enums)
enum class Color {RED, GREEN, BLUE}; 
enum class Feelings {EXCITED, MOODY, BLUE};

The use of the word class is meant to indicate that each enum type really is different and not comparable to other enum types. Strongly typed enums, enum classes, also have better scoping. Each enum value is scoped within the name of the enum class. In other words, to access the enum values, you must write:

Color color = Color::GREEN;
if ( Color::RED == color )
{
    // the color is red
}

Old style C++ enums are still available--if you want them--largely for backward compatibility with existing code bases. They did pick up one trick; you can now, optionally, put the enum name in front of the value: Color::RED. But since this is optional, it doesn't solve any naming conflicts; it just makes it a little bit more clear.

Enum classes have another advantages over old-style enums. You can have a forward declaration to a strongly typed enum, meaning that you can write code like:

enum class Mood;

void assessMood (Mood m);
 
// later on:
enum class Mood { EXCITED, MOODY, BLUE };

Why would this be useful? Forward declarations are often about the physical layout of code on disk into different files or to provide opaque objects as part of an API. In the first case, where you care about the physical disk layout, using a forward declaration allows you to declare an enum type in the header file while putting specific values into the cpp file. This lets you change the list of possible enum values quite frequently without forcing all dependent files to recompile. In the second case, an enum class can be exposed as a type-safe but otherwise opaque value returned from one API function to be passed into another API function. The code using the API need not know the possible values the type can take on. Since the compiler still knows about the type, it can enforce that variables declared to work with that type are not confused with variables working with another type.

Well-defined enum sizes

A final advantage of enum classes is that you can set the size of your enum--you can use any signed or unsigned integer type. It defaults to int, but you can also use char, unsigned long, etc. This will ensure some measure of compatibility across compilers.

// we only have three colors, so no need for ints!
enum class Colors : char { RED = 1, GREEN = 2, BLUE = 3 };

But in C++11, we can do even better, specifying exact sizes for enums, using cstdint.

<cstdint>

One problem that C++ has suffered from is a lack of standard types that provide fixed, well-defined sizes. For example, sometimes you want to have a 32-bit integer, not just an int that might have different sizes on different architectures. In C++11, the C99 header file stdint.h has been included as cstdint. The cstdint header includes types such as std::int8_t, std::int16_t, std::int32_t, and std::int64_t (as well as unsigned versions that begin with u: std::uint8_t).

Here's an example that combines these new types with enum classes to get completely known sizes for your enum across compilers and architectures:

#include <cstdint>
enum class Colors : std::int8_t { RED = 1, GREEN = 2, BLUE = 3 };

nullptr

In C and C++, it's always been important to express the idea of a NULL pointer--one that has no value. Oddly, in C++, the expression used, 0 (or NULL, always #defined to zero) was not even a pointer type. Although this worked most of the time, it could lead to strange and unexpected problems in what are, admittedly, rather edge cases. For example imagine you have the following two function declarations:

void func(int n); 
void func(char *s);

func( NULL ); // guess which function gets called?

Although it looks like the second function will be called--you are, after all, passing in what seems to be a pointer--it's really the first function that will be called! The trouble is that because NULL is 0, and 0 is an integer, the first version of func will be called instead. This is the kind of thing that, yes, doesn't happen all the time, but when it does happen, is extremely frustrating and confusing. If you didn't know the details of what is going on, it might well look like a compiler bug. A language feature that looks like a compiler bug is, well, not something you want.

Enter nullptr. In C++11, nullptr is a new keyword that can (and should!) be used to represent NULL pointers; in other words, wherever you were writing NULL before, you should use nullptr instead. It's no more clear to you, the programmer, (everyone knows what NULL means), but it's more explicit to the compiler, which will no longer see 0s everywhere being used to have special meaning when used as a pointer.

std::nullptr_t

nullptr, by the way, is not only declared to be a pointer and convert implicitly to all pointer types (and bool), but it is its own special, distinct type:

decltype( nullptr )

While we can use decltype to extract its type, there is also a more convenient notation:

std::nullptr_t

Since nullptr is its own unique type, you can use it as a constructor or function argument when you want to be sure that you only ever take an empty pointer for a value. For example:

void func( std::nullptr_t );

declares a function that takes only nullptr (or a value cast to std::nullptr_t) and nothing else, a rather neat trick.

Regardless of all this--the rule of thumb for C++11 is simply to start using nullptr whenever you would have otherwise used NULL in the past.

Previous: Faster Code with Rvalue References and Move Semantics Learn how C++11 lets you write faster code in yet another way, by avoiding slow copies in favor of fast moves