Hello and welcome. I know it’s been a few days and in today’s information age that’s half an eternity, but we have been studying typing. There we learned about the primitive data types of C++.

With their help, you can already restrict value ranges very well and ensure type safety. However, if you need more complex data types or if they are too unique for your use case, you are free to say:

“That’s not good enough for us!”

Fortunately, C++ lets you compose and define your own from the basic data types.

Enumerations - enumeration type

There are situations where a variable should only accept certain values and maybe the values don’t even match any numeric type. In this case enumerations are your tool of choice.

Starting with keyword enum you can assign a type name and create a listing of the values of your custom data type. The single elements of your listing are called enumerators. Then you can directly declare variables of your new datatype. It is important to note that the definition of your new datatype is terminated with a semicolon ;. It doesn’t matter if you declare variables already or not.

// listing1: enums syntax

enum class TypName
{
    ListOfValues;
} ListOfVariables;

So much for the syntax of enumerations. The enumeration types have their names because they internally map their values to the natural numbers. Your listed values are numbered starting with 0.

With an example the functionality will surely become clearer. Our new datatype ‘cardinalDirection’ represents the four cardinal directions. After defining the enumeration, the variable myDirection is assigned the value East.

// listing2: cardinalDirection enumeration

enum class cardinalDirection
{
    North,  // = 0
    East,   // = 1
    South,  // = 2
    West    // = 3
} myDirection;

int main()
{
    myDirection = cardinalDirection::East;

    return 0;
}

What happens in listing2? Introduced by the keyword enum with the addition class the data type cardinalDirection is defined globally. Inside the curly brackets the values North, East, South and West are listed as the only valid values of this type, each separated by a comma. After the closing parenthesis, the first variable myDirection of our data type is declared directly. The following semicolon indicates the end of the definition.

Following this, in the main() function East is assigned as the value of the variable. Only one of the four values from our definition is accepted as valid. As you can see, the syntax does not require any special characters. This is possible because the datatype cardinalDirection explicitly accepts the value East.

Internally, however, it looks quite different. The compiler converts the enumerators to integer values. This means that a number is assigned to each value listed in the enumration. In our example North gets 0, East gets 1, South gets 2 and West gets 3. This is the default case. If you want, you can assign any number for each value in the definition.

// listing3: enumeration with custom interger values

enum class cardinalDirection
{
    North   =   78,
    East    =    6,
    South   =    0,
    West    =   -1
} myDirection;

Why this “class”?

Often you will find in the source code of others the enumeration declaration without the addition class. The keyword was introduced in C++ in 2011. You can leave it out and still have valid C++. But the usage makes sense. class strengthens type safety. This prevents implicit conversions of enumeratons to int.

// listing4: implicit converting to int

// enum
enum colorRGB { red, green, blue };

int i = red + blue;     // equivalent to 0 + 2

// enum class
enum class colorCMYK {cyan, magenta, yellow, key};

int j = colorCMYK::cyan + colorCMYK::magenta; // error

Besides the implicit conversion of red and blue to 0 us 2 it is noticeable that the type qualification color:: may be missing. The exact difference between the two variants is that in enum class the enumerators exist locally in the enumeration. Therefore, no implicit conversion to other types can take place. In simple enum the enumerators are in the same scope as the enumeration and the implicit conversion works.

But because of the increased type safety you should use enum class. Of course, you are free to decide for yourself in the particular situation and to do without it in justified exceptional cases.

Of course, enum class does not remove the possibility of conversion, but requires you to give this instruction deliberately.

The direct comparison

// listing5: comparison enum and enum class

#include <iostream>

int main()
{
    // enum
    enum colorRGB
    {
        red,
        green,
        blue
    } colorAdd;

    colorAdd = red; // assign

    int i = colorAdd; // implicit convertion

    colorAdd = (colorRGB)2; // old style casting

    std::cout << "Converted Interger: " << i << " Convereted Enum: " << colorAdd << std::endl; // cout of the enum, impicit convertion

    // enum class
    enum class colorCMYK
    {
        cyan,
        magenta,
        yellow,
        key
    } colorSub;

    colorSub = colorCMYK::key; // assign

    int j = static_cast<int>(colorSub); // casting to int

    colorSub = static_cast<colorCMYK>(1); // casting to enum class

    std::cout << "Converted Interger: " << j << " Convereted Enum: " << static_cast<int>(colorSub) << std::endl; // cout of the enum class

    return 0;
}

Since enumerators are internally mapped as integers, you can also assign any number to the enumeration. But make sure that a corresponding enumerator exists for the number.

So it doesn’t matter if you assign a number or a valid value to the variable. The result is the same. However, you must explicitly convert the number to a value of the type of the enumeration with a cast.

Type conversion

“What is static_cast and what does this cast he is talking about mean?”

Most of the time type conversions are a sign of not a well thought out program design. So you’d better think about it again. But there are use cases where conversions are necessary.

I don’t want to go too deep into casting at this point. But now I have used castings in our examples to enumerations. Therefore, there is a brief overview.

When we declare a variable, we specify its data type in the process. The type of a variable cannot be changed afterwards. If a constellation arises, in which you would like to further use the value of a variable, but the data type does not fit, then you can convert the type into the suitable one. This type conversion is called casting.

Implicit and Explicit

The origin of C++ is in the C programming language, and compilers try to remain backwards compatible so that they can continue to build older code. In C, there are the two variants of implicit and explicit casts.

In an implied cast, the compiler tries to make assumptions about the programmer’s intentions instead of throwing data type errors. It converts the types automatically.

// listing6: implicit casting

#include <iostream>

int main()
{
    float f = 1.5;
    int i = f;      // implicit cast

    std::cout << "Float " << f << " converts to Int " << i << std::endl;

    return 0;
}

Implicit casting does not always work and is up to the compiler to decide. To have more control over when and how the type is converted, you can also give the instruction explicitly. All you have to do is to put the desired data type in brackets in front of the corresponding variable (see listing 7).

// listing7: explicit casting

#include <iostream>

int main()
{
    int dividend = 3;
    int divisor = 4;

    float quotient = dividend/divisor;              // result = 0
    std::cout << " Without explicit cast: " << quotient << std::endl;

    float quotientCast = (float)dividend/divisor;   // result 0.75
    std::cout << " With explicit cast: " << quotientCast << std::endl;

    return 0;
}

C is not very strict when it comes to castings. If you explicitly give the instruction, then it is converted without checking. And that even with pointers. For this reason, C-style type conversions are not without danger and are not recommended by C++ programmers.

The C++ Casting Operators

Even though type conversions strongly threaten type safety, they still cannot be simply discarded. In some situations they are helpful and solve compatibility problems. That’s why C++ introduces four new casting operators that provide more control and security:

  • static_cast
  • dynamic_cast
  • reinterpret_cast
  • const_cast

The fact that there are now four specialized operators and no longer just one cast for everything avoids unwanted effects. In addition, these casts are visually easy to recognize because of their syntax and make the programmer more aware of their use.

// listing8: syntax of the casting operators

destination_type result = cast_operator<destination_type> (object_to_cast);

I don’t want to go into detail about all of them here. But I would like to introduce static_cast. In Listing5 I used the operator to convert the enumerator of our own datatype into an integer for output with std::cout.

static_cast is intended for converting standard data types and should be your first choice in most cases. The operator does the job of implicit casting and gives you the biggest advantage when explicitly converting pointers. In other words, we are drifting into an advanced topic.

Just remember as a conclusion: To not endanger the type safety of your source code more than necessary, use static_cast and not the old variants. Keep your authority and don’t give the responsibility to the compiler.

That sticks

Our first own data type is an enumeration. We have defined the valid values and the value range ourselves, even defined the first variables. Internally, our values are mapped to integers and numbered consecutively. However, we can also define the numbering ourselves.

We looked at how to increase type safety for our data types with enum class and how to convert to another type with static_cast.

A variable of our enumeration datatype can not only be defined locally or globally. You can also use it inside a structure. We will get to know these next time.

Until then I wish you maximum success!


Sources

  • [1] B. Stroustrup, A Tour of C++. Pearson Education, 2. Auflage, 29. Juni 2018.
  • [2] B. Stroustrup, Programming: Principles and Practice Using C++. Addison Wesley, 2. Auflage, 15. Mai 2014.
  • [3] U. Breymann, Der C++ Programmierer. C++ lernen – professionell anwenden – Lösungen nutzen. Aktuell zu C++17. München: Carl Hanser Verlag GmbH & Co. KG; 5. Auflage, 6. November 2017