5.8.1 Enumerations And Scope

The text previously defined Scope as "the location in a program where a specific name is visible and accessible." Scope is a property of any programming element a programmer names: variables, functions, structures, classes, enumerations, etc. The name becomes a symbol in the program. The compiler component saves the symbol in its symbol table, mapping the symbol name to the corresponding element's address in the case of a definition. Programmers typically specify basic enumerations in global scope, giving their symbolic constants global scope. However, global scope often causes problems.

Global-Scope Conflicts

enum hurricane { GAMMA, LAURA, MARCO };
enum radiation { ALPHA, BETA, GAMMA };
enum function { FACTORIAL, GAMMA };
Enumeration conflict example. Imagine two enumerations specified in global scope. Individually, neither enumeration causes a problem, but they conflict when specified in the same scope. The first enumeration set GAMMA to 2, while the second the same name to 1. When compiled, the compiler detects and reports the conflict with a diagnostic such as redeclaration of 'GAMMA' and aborts the compilation.

If the enumerations are part of only one program, programmers can easily rename the enumeration elements: radiation_ALPHA and function_GAMMA. Aside from being inelegant, this approach fails when a program utilizes the client-supplier organization, bringing both suppliers together in a single application client. If each enumeration is part of an otherwise independent supplier, programmers may not have the supplier's source code, making it impossible to rename the enumeration elements.

The Scope Resolution Operator

We first encountered the scope resolution operator during the introduction of C++'s I/O system. Two adjacent semicolons, without any intervening space, form it. While it can operate either as a unary or binary operator, the latter is the most frequently used version. The binary version is left associative, processing its operands from left to right.

Programmers narrow a feature's scope by enclosing it in a container: a structure, namespace, or class. In this sense, enumerations are containers holding a set of symbolic constants. C++ also allows the nesting of one container in another. In conjunction with a container, the operator permits programmers to manage the scope of a programming element. The left-hand operand names a scoping container, while the right-hand operand names the final feature or a nested container. In the following example, programmers use it to select elements specified in a specific enumeration or structure.

Embedded Enumerations

The conflicting enumerations, radiation and function, each have a well-defined meaning within a given context:

These observations suggest that including GAMMA in multiple enumerations and using them in the same program should be valid. But specifying the various GAMMA elements in the same (global) scope causes the conflict. We can limit their scope, thereby eliminating the conflict, by embedding the enumerations inside a structure and extending the access syntax. (The following examples demonstrate syntax but don't otherwise solve a "real" problem.)

#include <iostream>
#include <string>
using namespace std;

enum hurricane { GAMMA, LAURA, MARCO };

struct Decay
{
	enum radiation { ALPHA, BETA, GAMMA };
	string		isotope;
	radiation	mode;
	double		halflife;
};

struct Math
{
	enum function { FACTORIAL, GAMMA };
};
int main()
{
	int	h1 = GAMMA;
	int	h2 = ::GAMMA;

	Decay	pu = { "Pu-239", Decay::radiation::GAMMA, 2.41e4 };
	int     i2 = pu.mode;
	cout << pu.mode << endl;
	int     i1 = Decay::radiation::GAMMA;

	int	m = Math::GAMMA;

	return 0;
} 
 
 
 
 
(a)(b)
Embedding an enumeration in a structure. Programs can only access or use enumerations embedded in structures through the structure name. In this example, the hurricane specification remains in global scope, but radiation and function are specified inside different structures. Although each enumeration has an element named GAMMA, their specifications are in different scopes and don't conflict.
  1. Programmers may specify any number of enumerations in global scope if the identifiers (the optional specification name and the elements) are unique within the specifying scope. radiation and function are specified inside the Decay and function structures, restricting the scope of each one to the specifying structure. Programmers typically put the enumeration and structure specifications in a header file, but they must occur before the program uses the name.
  2. When accessing a global enumeration, the unary scope resolution operator is optional but is required when accessing an embedded enumeration. The binary version is evaluated from left to right, binding its right-hand operand to the scope defined by its left-hand operand. Reading in evaluation order, left to right, we can "decode" the expression Decay::radiation::GAMMA as follows:
    • Decay::radiation means the name radiation is declared or specified in the Decay structure.
    • radiation::GAMMA means the name GAMMA is declared or specified in radiation enumeration.
    The GAMMA specified in radiation is independent of and distinct from the GAMMA declared in the other enumerations.
Although C provides both enumerations and structures, it does not have the scope resolution operator and, therefore, cannot support nested enumerations.

Scoped Enumerations

Scoped enumerations are similar in appearance and simplicity to the initial, conflicting example - nevertheless, a simple syntax update made in 2011 resolves the conflict. Adding one of two existing keywords to the enumeration specification creates a new, named scope and binds the enumeration specification to it. Programmers can choose either enum class or enum struct; both versions have the same effect, but the first is preferred.

#include <iostream>
using namespace std;

enum Hurricane { GAMMA, LAURA, MARCO };
enum class radiation { ALPHA, BETA, GAMMA };
enum class function { FACTORIAL, GAMMA };

int main()
{
	int	  h1 = GAMMA;
	int	  h2 = ::GAMMA;
	function  f = function::GAMMA;
	radiation test = radiation::GAMMA;

	switch (test)
	{
		case radiation::ALPHA :
			cout << "radiation Alpha\n";
			break;
		case radiation::BETA :
			cout << "radiation Beta\n";
			break;
		case radiation::GAMMA :
			cout << "radiation Gamma\n";
			break;
	}
    
	return 0;
}
Scoped and unscoped enumeration example. The example leaves Hurricane unscoped while making radiation and function scoped. The enumeration tag or name is optional for unscoped but required for scoped enumerations. The program uses the scope resolution operator to disambiguate the enumeration elements as it did with embedded enumerations.