Skip to content

Instantly share code, notes, and snippets.

@NTimmons
Created May 7, 2022 21:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NTimmons/a7229b1dacf8280be2292ebd6b2b8b5d to your computer and use it in GitHub Desktop.
Save NTimmons/a7229b1dacf8280be2292ebd6b2b8b5d to your computer and use it in GitHub Desktop.
C++ Enum examples and rules
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