Strongly-Typed Enums
Enumerations are a convenient way to define integer constants with names. These integer constants are called enumerators. Sadly, classical enums have a few drawbacks.
The drawbacks of enumerations in classical C++
A short reminder. Three drawbacks of enumerations.
- The enumerators implicitly convert to int.
- They introduce the enumerators in the enclosing scope.
- The type of enumeration can not be specified.
First to point 3: Enumerations can not be forward declared because their type is not known. There is only a guarantee for the enumerators in classical C++. The type must be integral and big enough to hold the enumerators.
Point 1 and point 2 are more surprising.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// enumClassic.cpp #include <iostream> int main(){ std::cout << std::endl; enum Colour{red= 0,green= 2,blue}; std::cout << "red: " << red << std::endl; std::cout << "green: " << green << std::endl; std::cout << "blue: " << blue << std::endl; int red2= red; std::cout << "red2: " << red2 << std::endl; // int red= 5; ERROR } |
On the one hand are the enumerators red, green, and blue, known in the enclosing scope. Therefore, the definition of the red variable in line 19 is impossible. On the other hand, red can be implicitly converted to int.
If you use no name for an enumeration like enum{red, green, blue}, the enumerators will be introduced in the enclosing scope.
But that surprise ends with C++11.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
Strongly-typed enumerations
The strongly-typed enumerations have to follow stronger rules:
- The enumerators can only be accessed in the scope of the enumeration.
- The enumerators don’t implicitly convert to int.
- The enumerators aren’t imported in the enclosing scope.
- The type of enumerators is, by default, int. Therefore, you can forward the enumeration.
The syntactical difference between the classic enumerations and the strongly-typed enumerations is minimal. The strongly-typed enumerations additionally get the keyword class or struct.
If you want to use an enumerator as an int, you must explicitly convert it with static_cast
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// enumCast.cpp #include <iostream> enum OldEnum{ one= 1, ten=10, hundred=100, thousand= 1000 }; enum struct NewEnum{ one= 1, ten=10, hundred=100, thousand= 1000 }; int main(){ std::cout << std::endl; std::cout << "C++11= " << 2*thousand + 0*hundred + 1*ten + 1*one << std::endl; std::cout << "C++11= " << 2*static_cast<int>(NewEnum::thousand) + 0*static_cast<int>(NewEnum::hundred) + 1*static_cast<int>(NewEnum::ten) + 1*static_cast<int>(NewEnum::one) << std::endl; } |
To calculate or output the enumerators, you must convert them into integral types. Either the addition or the output of strongly-typed enumerations is defined.
I often speak in this post about classical versus strongly-typed enumerations. Often there are called scoped and unscoped enumerations.
Explicitly specifying the type
I ignored one feature of the enumerations in C++11. You can explicitly specify the type of enumerators. By default, it’s int.
But that does not have to be. You can use integral types like bool, char, short int, long int, or, long long int. Read msdn.microsoft.com for the details. You can read in my post, Check types how you can check at compile time if a type is integral.
You can independently use the scoped property and the explicit type specification of an enumeration. Dependent on the base types, the enumerations have different sizes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
// enumType.cpp #include <iostream> #include <climits> enum struct Colour0: bool{ red, // 0 blue // 1 }; enum Colour1{ red= -5, blue, // -4 green // -3 }; enum struct Colour2: char{ red= 100, blue, // 101 green // 102 }; enum class Colour3: long long int{ //red= std::numeric_limits<long long int>::min(); red= LLONG_MIN, blue, // std::numeric_limits<long long int>::min() + 1 green // std::numeric_limits<long long int>::min() + 2 }; int main(){ std::cout << std::endl; std::cout << "sizeof(Colour0)= " << sizeof(Colour0) << std::endl; std::cout << "sizeof(Colour1)= " << sizeof(Colour1) << std::endl; std::cout << "sizeof(Colour2)= " << sizeof(Colour2) << std::endl; std::cout << "sizeof(Colour3)= " << sizeof(Colour3) << std::endl; std::cout << std::endl; std::cout << "Colour0::red: " << static_cast<bool>(Colour0::red) << std::endl; std::cout << "red: " << red << std::endl; std::cout << "Colour2::red: " << static_cast<char>(Colour2::red) << std::endl; std::cout << "Colour3::red: " << static_cast<long long int>(Colour3::red) << std::endl; } |
My in Microsoft Visual Studio 12.0 included C++ compiler cl.exe can not evaluate the expression std::numeric_limits<long long int>::min() (line 24) at compile time. According to the C++11 standard, is std::numeric_limits<long long int>::min() a constant expression. Therefore, I can use this expression to initialize an enumerator. Because of the missing feature in cl.exe, I have to use the macro LLONG_MIN in line 25. This macro is defined in the same header as the expression std::numeric_limits: <climits>.
At the end, the output.
What’s next?
Typically, you have in the embedded world a system of systems. Or, to say it differently: Many autonomous systems interact with each other to build the whole system. If I change the term autonomous system with the object, we are in the domain of object-oriented programming. From my perspective, object-oriented abstraction is an abstraction with significant added value for a deeper understanding of embedded systems. Therefore, I will write in the next post about the new keywords override and final, which empowers you to manage the object hierarchies.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!