The basic enumeration syntax forms a list of simple named integers or symbolic constants. The compiler can set default values for each constant, or programmers can establish the values. Programmers can name enumerations, creating a new program type specifier. Enumerations further extend the number of ways that programmers can create symbolic constants, each with relative advantages and disadvantages.
Symbolic, Named, or Manifest Constants
The terms symbolic constant, named constant, and manifest constant are synonyms for naming a constant value. As the term implies, a constant is a value that does not change while the program runs. For example, 5 is an integer constant, 5.0 is a double constant, '5' is a character constant, and "5" and "Hello World" are string constants (also known as string literals). C++ provides three ways of naming constants:
The enum Syntax
The original C-style enumerations were simple and unscoped, but they have evolved with C++, growing to support the object-oriented paradigm and additional data types. Although the text continues to focus on simple, unscoped enumerations, the following figure presents a more complete description of the modern syntax. By now, we are familiar with the idea that a program consists of a pattern of keywords, symbols, and programmer-supplied names. The specification of an enumeration follows this same basic approach. Some parts of an enumeration are required, while others are optional.
Element
Role
Required
enum enum class enum struct
Keyword
yes (1 of 3)
name
Also called a tag, this is the name or identifier a programmer gives to the enumeration, forming a new data type. The name is optional for traditional enumerations but required for scoped ones
no
: type
A kind of an integer: char, short, int, etc. The ANSI 2003 standard allows the implementation to choose the integer size, but this feature overrides that choice
no
{
Delimiter
yes
element list
Comma-separated list of names (these are the names of the symbolic constants). Strictly speaking, this list is not required, but we're going to treat it as if it was
yes - one or more
= value
If a value is specified, it overrides the default value. Default values begin at 0 for the first element and then count sequentially for subsequent elements. The value can be a constant expression; e.g., if "circle" appears to the left of "square," then square = circle + 4 is legal
no
}
Delimiter
yes
variable list
Comma-separated list of enumeration variables
no
;
Statement terminator
yes
The enumeration syntax. Although the complete enumeration syntax is quite complex, only a few elements are necessary in practice. The illustration colors the traditional enumeration elements in blue and colors the elements specific to scoped enumerations in green. The textbook focuses on the traditional (blue) enumeration elements and briefly discusses scoped enumerations later.
The many varied paths through the syntax diagram notwithstanding, the enumerations programmers use most frequently in practice are generally quite simple. The following examples illustrate common and a few less common uses.
The enumeration list. The enumeration list is essential to the enumeration, where it does the "real work." It is very flexible, providing many different ways of initializing symbolic constants.
A single element/value pair creates a single symbolic constant, replacing #define or const.
Enumerations can assign unique values to each element in the list, replacing a series of #define directives or const statements.
Whenever an enumeration does not explicitly assign a value to an element, it automatically increments the previous value. So, alpha = 3, beta = 4, and gamma = 5.
Enumerations support simple constant arithmetic. Element assignment occurs left to right, and each part of the expression to the right of the assignment operator must be an established constant value when assignment occurs. So, alpha = 3, beta = 5, and gamma = 8.
enum shape { circle, square, triangle };
shape my_shape; shape your_shape;
my_shape = square; // all compilers
my_shape = 1; // some compilers
(a)
(b)
The enumeration tag (name). The enumeration tag or name is an optional name a programmer gives to an enumeration. Unlike the other data structures this chapter covers, (unscoped) enumerations are typically unnamed. However, when named, the name forms a new data type or type specifier.
Programmers use type specifiers to define variables. The tag name, shape, specifies the variable type, while my_shape and your_shape are the defined variables' names.
Once my_shape is defined, it is possible to save shape values to it. The shape type is a sub-range of the integers, so some, but not all, compilers allow programmers to save an integer.
As the size and complexity of programs increase, programmers typically break them into multiple files. Programmers frequently put enumerations in header files (files ending with a .h extension) and #include them in source code files (files ending with a .cpp extension). We'll explore this organization in detail later in the chapter. With enumerations in header files and variable definitions in source code files, programmers use variable lists infrequently in practice.
Enumeration Examples
It's easier to understand enumerations by seeing them in a concrete example demonstrating how and why programmers use them.
Programming Example 1: Eliminating Magic Numbers
A "magic number" is an arbitrary value the program uses to pass information about events, notifications, or other simply-encoded information around a program. The exact value is insignificant, but it is essential that the value is unique within the given context and used consistently throughout the program. The following example illustrates magic numbers and the importance of their uniqueness and consistency.
"Magic Number" Version
Enumeration Version
int command = get_command();
switch(command)
{
case 0:
exit(0);
case 1:
search();
break;
case 2:
input();
break;
case 3:
import();
break;
case 4:
help();
break;
default:
cerr << "Unknown command\n";
break;
}
enum { EXIT, SEARCH, INPUT, IMPORT, HELP };
int command = get_command();
switch(command)
{
case EXIT:
exit(0);
case SEARCH:
search();
break;
case INPUT:
input();
break;
case IMPORT:
import();
break;
case HELP:
help();
break;
default:
cerr << "Unknown command\n";
break;
}
(a)
(b)
if (command == 2)
do_one_thing();
else if (command == 3)
do_another_thing();
if (command == INPUT)
do_one_thing();
else if (command == IMPORT)
do_another_thing();
(c)
(d)
Eliminating "magic numbers". The example excepts the switch statement from a simple database program. The program displays a menu, and the user enters a single command, such as "search." The function get_command reads the command from the console as a string and maps it to a unique integer, which it returns. The switch statement interprets the command by calling an appropriate function.
Imagine that the user enters the "search" command and get_command returns 1. If the program doesn't use "1" to denote any other command and always uses "1" to represent the search operation, then the program will run the search operation correctly. Even when there are no problems, case 1: does little to inform a reader what that particular case does - 1 is a "magic number" that doesn't have a natural connection to the operation it represents.
The second example is like the first but creates five symbolic constants with an enumeration. get_command still returns the same value but encoded with a statement like return SEARCH;. Each case now has a symbolic constant as its target, clarifying the case's purpose to a program reader: case SEARCH: conveys more meaning than case 1:.
A program reader can surmise the meaning of each case in (a) from the function it calls. But it's easy to imagine situations denying the reader even that meager information.
Symbolic constants don't replace appropriate program comments, but they do make it more self-documenting.
Programming Example 2: Naming Special Values
Unix and early versions of Linux managed file and directory access based on three users, each with three distinct permissions. This technique classifies the users as the file owner (called the user), group, and others and names the three permissions as read, write, and execute. The technique can represent each permission with a single bit: 1 indicates that the user has that permission, and 0 indicates that the user does not. That means that a sequence of nine bits summarizes a given file's access permissions.