Enumerations let us group together sets of integral constants. Like classes, each enumeration defines a new type. Enumerations are literal types (§ 7.5.6, p. 299).
C++ has two kinds of enumerations: scoped and unscoped. The new standard introduced scoped enumerations. We define a scoped enumeration using the keywords enum class
(or, equivalently, enum struct
), followed by the enumeration name and a comma-separated list of enumerators enclosed in curly braces. A semicolon follows the close curly:
enum class open_modes {input, output, append};
Here we defined an enumeration type named open_modes
that has three enumerators: input
, output
, and append
.
We define an unscoped enumeration by omitting the class
(or struct
) keyword. The enumeration name is optional in an unscoped enum
:
enum color {red, yellow, green}; // unscoped enumeration
// unnamed, unscoped enum
enum {floatPrec = 6, doublePrec = 10, double_doublePrec = 10};
If the enum
is unnamed, we may define objects of that type only as part of the enum
definition. As with a class definition, we can provide a comma-separated list of declarators between the close curly and the semicolon that ends the enum
definition (§ 2.6.1, p. 73).
The names of the enumerators in a scoped enumeration follow normal scoping rules and are inaccessible outside the scope of the enumeration. The enumerator names in an unscoped enumeration are placed into the same scope as the enumeration itself:
enum color {red, yellow, green}; // unscoped enumeration
enum stoplight {red, yellow, green}; // error: redefines enumerators
enum class peppers {red, yellow, green}; // ok: enumerators are hidden
color eyes = green; // ok: enumerators are in scope for an unscoped enumeration
peppers p = green; // error: enumerators from peppers are not in scope
// color::green is in scope but has the wrong type
color hair = color::red; // ok: we can explicitly access the enumerators
peppers p2 = peppers::red; // ok: using red from peppers
By default, enumerator values start at 0 and each enumerator has a value 1 greater than the preceding one. However, we can also supply initializers for one or more enumerators:
enum class intTypes {
charTyp = 8, shortTyp = 16, intTyp = 16,
longTyp = 32, long_longTyp = 64
};
As we see with the enumerators for intTyp
and shortTyp
, an enumerator value need not be unique. When we omit an initializer, the enumerator has a value 1 greater than the preceding enumerator.
Enumerators are const
and, if initialized, their initializers must be constant expressions (§ 2.4.4, p. 65). Consequently, each enumerator is itself a constant expression. Because the enumerators are constant expressions, we can use them where a constant expression is required. For example, we can define constexpr
variables of enumeration type:
constexpr intTypes charbits = intTypes::charTyp;
Similarly, we can use an enum
as the expression in a switch
statement and use the value of its enumerators as the case
labels (§ 5.3.2, p. 178). For the same reason, we can also use an enumeration type as a nontype template parameter (§ 16.1.1, p. 654). and can initialize class static
data members of enumeration type inside the class definition (§ 7.6, p. 302).
So long as the enum
is named, we can define and initialize objects of that type. An enum
object may be initialized or assigned only by one of its enumerators or by another object of the same enum
type:
open_modes om = 2; // error: 2 is not of type open_modes
om = open_modes::input; // ok: input is an enumerator of open_modes
Objects or enumerators of an unscoped enumeration type are automatically converted to an integral type. As a result, they can be used where an integral value is required:
int i = color::red; // ok: unscoped enumerator implicitly converted to int
int j = peppers::red; // error: scoped enumerations are not implicitly converted
enum
Although each enum
defines a unique type, it is represented by one of the built-in integral types. Under the new standard, we may specify that type by following the enum
name with a colon and the name of the type we want to use:
enum intValues : unsigned long long {
charTyp = 255, shortTyp = 65535, intTyp = 65535,
longTyp = 4294967295UL,
long_longTyp = 18446744073709551615ULL
};
If we do not specify the underlying type, then by default scoped enum
s have int
as the underlying type. There is no default for unscoped enum
s; all we know is that the underlying type is large enough to hold the enumerator values. When the underlying type is specified (including implicitly specified for a scoped enum
), it is an error for an enumerator to have a value that is too large to fit in that type.
Being able to specify the underlying type of an enum
lets us control the type used across different implementations. We can be confident that our program compiled under one implementation will generate the same code when we compile it on another.
Under the new standard, we can forward declare an enum
. An enum
forward declaration must specify (implicitly or explicitly) the underlying size of the enum
:
// forward declaration of unscoped enum named intValues
enum intValues : unsigned long long; // unscoped, must specify a type
enum class open_modes; // scoped enums can use int by default
Because there is no default size for an unscoped enum
, every declaration must include the size of that enum
. We can declare a scoped enum
without specifying a size, in which case the size is implicitly defined as int
.
As with any declaration, all the declarations and the definition of a given enum
must match one another. In the case of enum
s, this requirement means that the size of the enum
must be the same across all declarations and the enum
definition. Moreover, we cannot declare a name as an unscoped enum
in one context and redeclare it as a scoped enum
later:
// error: declarations and definition must agree whether the enum is scoped or unscoped
enum class intValues;
enum intValues; // error: intValues previously declared as scoped enum
enum intValues : long; // error: intValues previously declared as int
Because an object of enum
type may be initialized only by another object of that enum
type or by one of its enumerators (§ 19.3, p. 833), an integral value that happens to have the same value as an enumerator cannot be used to call a function expecting an enum
argument:
// unscoped enumeration; the underlying type is machine dependent
enum Tokens {INLINE = 128, VIRTUAL = 129};
void ff(Tokens);
void ff(int);
int main() {
Tokens curTok = INLINE;
ff(128); // exactly matches ff(int)
ff(INLINE); // exactly matches ff(Tokens)
ff(curTok); // exactly matches ff(Tokens)
return 0;
}
Although we cannot pass an integral value to an enum
parameter, we can pass an object or enumerator of an unscoped enumeration to a parameter of integral type. When we do so, the enum
value promotes to int
or to a larger integral type. The actual promotion type depends on the underlying type of the enumeration:
void newf(unsigned char);
void newf(int);
unsigned char uc = VIRTUAL;
newf(VIRTUAL); // calls newf(int)
newf(uc); // calls newf(unsigned char)
The enum Tokens
has only two enumerators, the larger of which has the value 129
. That value can be represented by the type unsigned char
, and many compilers will use unsigned char
as the underlying type for Tokens
. Regardless of its underlying type, objects and the enumerators of Tokens
are promoted to int
. Enumerators and values of an enum
type are not promoted to unsigned char
, even if the values of the enumerators would fit.