Week 7

Enumerated Types

The Date plus the Weekday enumerated type.

Single value types are called scalar types. All of the built-in, primitive data types—int, char, bool and double—are scalar data types. The Date type, which you met in the last lesson, was a user-defined structured type.

The Weekday type shown here above the Date is a user-defined scalar type which contains only a single simple value.

You may define your own new scalar types by listing the elements in their domain. Such types are called enumerated types. Enumerated types are based upon another type, in this case the integers. Such types are called derived or compound types.

The syntax for defining an enumerated type is:

enum class type-name { name-list };   // C++ 11 scoped enums
enum type-name { name-list };         // traditional "plain" enums

Here type-name is the name of the type and name-list is a list of literals representing the values in the domain, separated by commas. The name-list does not need any semicolons, unlike regular variable definitions. However, you must end the definition with a semicolon.

The scoped enumeration was added in C++11, while the older type is called an un-scoped or plain enumeration. In this class we'll use the newer, scoped enumerations, since they will help you avoid all kinds of scope bugs.

Week 7

Defining Enumerated Types

Here is the definition of the Weekday type mentioned at the beginning of the lesson. The C++ compiler assigns values to the names. sunday is assigned 0, monday is assigned 1, and so on.

enum class Weekday
{    
    sunday, monday, tuesday, wednesday, thursday, 
    friday, saturday
};

You may also explicitly specify the underlying integer value used to represent any or all of the literals of an enumerated type. For example, the Coin type represents U.S. coins where each literal is equal to the monetary value of that coin.

enum class Coin
{
    penny = 1, nickel = 5, dime = 10,
    quarter = 25, halfDollar = 50, dollar = 100
};

If you supply initializers for some values but not others, the compiler will automatically number the remaining literals consecutively after the last.

enum class Month
{
    jan = 1, feb, mar, apr, may, jun, jul, aug,
    sep, oct, nov, dec
};

Here, jan has the value 1, feb has the value 2, and so forth up to dec, which is 12.

In previous semesters, the Course Reader used UPPER_CASE for the enumerated elements, since that is the convention in Java. Looking at several style guides (both Google and the Core Guidelines), we've switched to lower-camelCase in this edition.

Week 7

Enumerated Variables

Just like the other types you’ve seen, you can create a variable of an enumerated type and initialize the variable with a scoped member of the type like this:

Coin c1 = Coin::penny;
// Coin c2 = 1;   // error

Note that you can't initialize the variable c2 with its underlying int representation. The second line in the example above is an error. You may, however, initialize or assign an integral value, by explicitly using a static_cast.

Coin c3 = static_cast<Coin>(5); // OK, but why do this?
Coin c4 = static_cast<Coin>(3); // Just wrong. No 3-cent coin

As you can see, using static_cast in this way is error prone, and turns off the error checking that C++ provides. The variable c4 in the example above is simply undefined. Don't do this.

However, if you want to get the "underlying" value of an enumerated type, you can use static_cast<int>(c) where c is a Coin variable like those shown above. Unlike casting from int to Coin, casting from Coin to int is always safe.

Week 7

Enumerated Output

The names of the enumerated values are not strings; you cannot print them:

Month m1{Month::jan};
cout << m1 << endl; // does not compile

Since enumerations are constant integral scalar values, you can use enum variables as switch selectors. Thus you could write a to_string() function like this:

string to_string(Month m)
{
    switch (m)
   { 
        case Month::jan: return "January";
        case Month::feb: return "February";
        case Month::mar: return "March";
        . . .
        default: return "INVALID MONTH";
   } 
}

This function converts a Month variable to a string so you can print it or concatenated it.

  • Enumerated types are internally just integers: pass them by value.
  • Each case label must use the fully qualified enumeration literal.
  • The default case returns an error if m does not match any Month. You may want to use an assertion instead, since it is definitely a programming error if the default is ever reached.
Week 7

Enumerated Input

Enumerated input is more difficult than output, because:

  • You have to decide how you want to encode the external version.
  • You cannot use the input operator (without overloading).
  • Because the external representation is text, you can't use a switch, but have to use sequential if..else if..else statements instead.
Month m1;
cin >> m1; // does not compile

Here's a version of a read_month function which reads from input, using the first three letters of the input to convert it to a Month:

istream& read_month(Month& m, istream& in = cin)
{
    string encoded;
    in >> encoded;
    string subs = encoded.substr(0, 3);   // first three characters
    for (char& c : subs) c = to_lower(c); // lowercase
    
    if (subs == "jan")      m = Month::jan;
    else if (subs == "feb") m = Month::feb;
    else if (subs == "mar") m = Month::mar;
    . . .
    else in.setstate(ios::failbit);   // set failbit
    return in;
}

You'd use the function like this:

cout << "Enter a month: ";
Month m;
if (read_month(m)) {
    cout << "The month is " << to_string(m) << endl;
}
else {
    cout << "You entered an invalid value." << endl;
}