C++: The Basics - eps1.6_Enumeration
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 thiscast
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