Created
May 7, 2022 21:58
-
-
Save NTimmons/a7229b1dacf8280be2292ebd6b2b8b5d to your computer and use it in GitHub Desktop.
C++ Enum examples and rules
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void Enum_Test() | |
{ | |
// Using Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30139 | |
// on VS2019. 07/05/2022 | |
// Enums are a type which are only able to represent a specifically chosen | |
// range of values. Each value is a named constant within the class. | |
std::cout << "===================\n" << "Start of Enum_Test().\n"; | |
// Simple Example: | |
// Declaration | |
enum ExampleEnum {A, B, C}; | |
// Usage | |
int a = A; | |
std::cout << "The integer a has the value " << a << " from the enum named ExampleEnum.\n"; | |
// or | |
int b = ExampleEnum::B; | |
std::cout << "The integer b has the value " << b << " from the enum named ExampleEnum.\n"; | |
// Values within an enum can be explicitly set, for example | |
enum SpecificEnum {D, E, F = 10}; | |
std::cout << "The Enum ExampleEnum has values: D:" << D << ", E: " << E << ", F: " << F << ".\n"; | |
// But what is the type of data stored in the enum? | |
std::cout << "D has type: " << typeid(D).name() << "\n"; | |
std::cout << "D has size: " << sizeof(D) << "\n"; | |
std::cout << "E has type: " << typeid(E).name() << "\n"; | |
std::cout << "E has size: " << sizeof(E) << "\n"; | |
std::cout << "F has type: " << typeid(F).name() << "\n"; | |
std::cout << "F has size: " << sizeof(F) << "\n"; | |
// What if we wanted to store a very large int in the enum? | |
// For example, max long (long has at least 64-bit int since C++11). | |
enum LargeEnum{G, H, I= _I64_MAX}; | |
// This causes "warning C4309: 'initializing': truncation of constant value". | |
// We are not able to fit the 64-bit value into the 32-bit enum. | |
// This is contrary to the C++ standard. | |
// As CppReference says: | |
// Values of unscoped enumeration type are implicitly-convertible to integral types. | |
// If the underlying type is not fixed, the value is convertible to the first type from | |
// the following list able to hold their entire value range: int, unsigned int, long, | |
// unsigned long, long long, or unsigned long long, extended integer types with higher | |
// conversion rank (in rank order, signed given preference over unsigned) (since C++11). | |
// If the underlying type is fixed, the values can be converted to their underlying type | |
// (preferred in overload resolution), which can then be promoted. | |
// https://en.cppreference.com/w/cpp/language/enum | |
std::cout << "The Enum LargeEnum has values: G:" << G << ", H: " << H << ", I: " << I << ".\n"; | |
std::cout << "I has type: " << typeid(I).name() << "\n"; | |
std::cout << "I has size: " << sizeof(I) << "\n"; | |
std::cout << "I has value: "<< I << "\n"; | |
// We can see that the enum value for I has been changed to -1 and it is still a 4 byte object in memory. | |
// No error is thrown for this use of I, as internally I is a 32-bit signed integer! | |
// Which may be deceptive and lead to bugs! | |
int CanThisBeInt_I = I; | |
std::cout << "CanThisBeInt_I has value: " << CanThisBeInt_I << "\n"; | |
// What if we use an unsigned 32-bit value? | |
enum UnsignedEnum { J, K, L = _UI32_MAX}; | |
std::cout << "L has type: " << typeid(L).name() << "\n"; | |
std::cout << "L has size: " << sizeof(L) << "\n"; | |
std::cout << "L has value: " << L << "\n"; | |
int CanThisBeInt_L = L; | |
std::cout << "CanThisBeInt_L has value: " << CanThisBeInt_L << "\n"; | |
// This results in the incorrect value and no warning! Thanks Microsoft! | |
// So now we know that enum implementation in the base configuration is a tad | |
// on the dangerous side. | |
// How can we make basic enums typed more safely? | |
// We apply a type at declaration! | |
enum IntTypedEnum: int { intA, intB, intC=10}; | |
std::cout << "The Enum TypedEnum has values: intA:" << intA << ", intB: " << intB << ", intC: " << intC << ".\n"; | |
enum UIntTypedEnum : unsigned int { uintA, uintB, uintC = _UI32_MAX}; | |
std::cout << "The Enum TypedEnum has values: uintA:" << uintA << ", uintB: " << uintB << ", uintC: " << uintC << ".\n"; | |
enum LLongTypedEnum : int64_t { uint64A, uint64B, uint64C = _I64_MAX }; | |
std::cout << "The Enum TypedEnum has values: uint64A:" << uint64A << ", uint64B: " << uint64B << ", uint64C: " << uint64C << ".\n"; | |
std::cout << "uint64A has type: " << typeid(uint64A).name() << "\n"; | |
std::cout << "uint64A has size: " << sizeof(uint64A) << "\n"; | |
std::cout << "uint64A has value: " << uint64A << "\n"; | |
// No warnings for any of the above, size and value are correct for 64-bit numbers. | |
// What about accessing enums differently? | |
// To help use enums like other types instead of like namespaces we have the | |
// class specifier. | |
enum class ClassEnum: int {classA, classB}; | |
// ClassEnum::classB cant be directly printed, as it is now an int inside a class and needs to be accessed. | |
std::cout << "Accessing ClassEnum::classB by :: = " << (int)ClassEnum::classB << "\n"; | |
// We can bring the class into another namespace for use with "using" | |
struct enclosing | |
{ | |
using enum ClassEnum; | |
}; | |
enclosing enc; | |
enc.classA; | |
// This lets us embed constants and options nicely into other namespaces/classes/structs | |
// without the additional indirection of an instance of that enum. | |
std::cout << "End of Enum_Test().\n" << "===================\n"; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment