- Imitation is the sincerest form of flattery
- Getting started
- Comparing apples to slightly nicer apples
- Backwards Compatibility
- Interface & Implementation
- Inheritance
- Other Benefits
- More to come
Imitation is the sincerest form of flattery
Recently, I’ve been working a fair amount with both C# and C++. One thing that I rather enjoyed about C# is the strongly typed enumerations. This one feature of C++ that I find lacking, even into modern C++17 standards. so I thought it might be a useful exercise to recreate the versatile and type-safe Enum class from C# in C++.
Getting started
I began this project originally as a byproduct of investigating anonymous enumerations in C++. Originally used in C and C++ as a constant that would require no space within an object, it was a way to decrease the overhead of normal static int
or char
constants that you might otherwise need. Below you can see an example of how one might use these anonymous enumerations.
There are a couple of important things to take a look at in this short snippet. First, the enumeration is not of type Color, instead it is a strange Color::<enum>
type. Therefore, it is not an instance of a Color object. Secondly, in order to use this enumeration in any useful way, you must explicitly cast the enumeration to its underlying value in order to make comparisons or assign to a variable. And finally, resulting from the previous two issues, it is difficult to create methods or operators to work with these enums
as they are unnamed.
Comparing apples and slightly nicer apples
C# Enumerations on the other hand, can be used in a much more elegant way. The System.Enum
class comes with a number of features that C++ enums lack - mostly since there is no reflection built-in to C++, which has its own historical baggage. Some of the best features of the System.Enum
class are the ability treat the enumerated values as flags, inherited ToString
methods, and the IsDefined
method. I’ve found the ToString
and IsDefined
methods to be extremely useful when attempting to using enumerations in game engines, as they dramatically reduce the code required to produce user-friendly text and provide safety checks on enumerated values, respectively.
Backwards Compatibility
One of the main goals with this project was to emulate the C# System.Enum
class without losing the syntax of regular enums
. Therefore, when possible, the syntax for enumerations remains the same. For example, these are some basic operations with standard enums
in C++:
Through switches, if-statements, assignment, and casting, we see how basic C++ enumerations can be used. We want to replicate that with our new-and-improved C++ classes.
Interface & Implementation
One of the beauties of using a class to implement our new C++ Enum
is that we gain the benefits of object oriented programming. For instance, we create an interface - seen below - to develop a simple means by which we interact with our Enums
. This interface also encapsulates the Type
enum from the outside scope, meaning that - if it was desired - you could keep the Type
variable and enum
protected and provide accessors that can further encapsulate your data.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Enum {
public:
typedef int size;
enum Type : size;
Type t_;
std::map<Enum, std::string> n_;
explicit Enum(int i) : t_((Type)i) {}
Enum(Type t) : t_(t) {}
operator Type() const { return t_; }
template <class T>
bool IsDefined(T e);
template <class T>
std::string GetName(T e);
};
There are a couple of important lines to note. For example, lines 3-4 and lines 7-9. We’ll take a look at each line to see what they’re for.
Starting with lines 3 and 4, we define the type size to be an alias of int
, then we use size to define the underlying type of our inner enum
. While it may seem convoluted, this means that we can define multiple classes with different underlying types. This could be useful to keep space limited if you know you only have a few values (some compilers might ignore your declaration of an underlying type smaller than an int
, but that’s neither here nor there). We can also define a macro that takes different typenames to create a variety of Enum
interfaces.
Next, we’ll move to lines 7, 8, and 9. Line 7 has a keyword that I had not seen until I started working on this project. The ‘explicit’ keyword informs the compiler that this specific conversion operator should only be used when it is explicitly invoked. Otherwise, it should be treated as if there was no conversion between the two types.
This is extremely useful when working with enumerations as they can be treacherous when using them alongside integers or converting between an enum
and its underlying type. The reason for this is that the compiler is allowed to use an additional conversion operator whenever an operator has been explicitly invoked. While this may not seem like an issue, consider the following:
Here, we see an addition (+
) operator being invoked to add two integers. Since if-statements take boolean values, the compiler must now convert from an integer value to a boolean. The compiler is allowed to do this conversion as an additional operation. However, this is being done implicitly, so to prevent this behavior may be hidden from the programmer. As such, it may cause errors to occur. To avoid this issue - also known as the safe bool problem - we must declare our conversion operator explicit
. The explicit
keyword can be used on conversion operators as well as constructors. In our case we use it with a Enum
constructor from an int
to avoid implicit construction of undefined Enum
values.
We can invoke the explicit
conversion operator by casting one type to another. The way this can be done is shown above. Here, we convert the long
typed integer to an integer of type int
by explicitly converting between the two. Let us consider how this keyword is used in our Enum
class.
On these two lines, we see a Enum
constructor from a Type
and an implicit conversion operator to a Type
. The constructor simply takes in a value of the same enumerated type as the underlying enum and set the value of t_
to that value. The user-defined conversion operator is declared on the second line of this snippet. It is defined by expressing the operator
keyword, a type (Type
in our case), and a code block that returns an object of that type. Since the explicit
keyword was not expressed, the compiler is allowed to use this conversion operator for both implicit and explicit conversions. These two lines are useful for converting to and from our Type
enum
without calling for an explicit conversion by casting.
Inheritance
As mentioned before, one of the benefits of utilizing a class to implement this Enum
is that we gain the benefits of working with objects. Some of those benefits (member defined operators, data encapsulation) were discussed previously, but we will now look at the importance of inheritance to this implementation. For example, we can create a sub-class of our Enum
type. Within this class we will define the enumerated values and map our values to strings for our GetName
method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Color : public Enum {
public:
enum Type : size {
RED,
YELLOW,
BLUE,
ORANGE,
GREEN
};
explicit Color(size s) : Color(Color::Type(s)) {}
Color(Enum e) : Enum(e){}
Color(Type t) : Enum(t) {}
operator Enum::Type() const { return t_; }
static bool IsDefined(Color c);
static std::string GetName(Color c);
private:
static std::map<Color, std::string> n_;
};
Here, we define a Color
class. In it, we declare five enumerated values for colors. We also see similar lines to that of the parent class, and we can take a look at those more closely.
In the following snippet (also, lines 10-13 in the longer snippet above), we see similar conversion operators as well as constructors. One thing to note is that we use the size
type rather than integer for the explicit
constructor. This way, we can inherit the defined type in the Enum
class and create multiple different parent Enum
classes with different enum
sizes, but all refer to that size with the same typename. This explicit Color
constructor takes in a size
(or, int
) and passes it to the Color
constructor for constructing a Color
from a Color::Type
. At the same time, it constructs a Color::Type
from the size
variable (s
) the constructor was called with. We do this so that we can explicitly cast integers to our enum, which is part of our desire to keep the syntax of this Enum
class the same as normal enums in C++;
In the next two lines, we see simple implicit constructors that will consume an Enum
or a Type
. This may be confusing but this Type
is a Color::Type
rather than an Enum::Type
which is why we need both constructors.
Finally, the implicit conversion operator to the parent Enum::Type
type is necessary to utilize operators defined for the Enum
class. Enum::Type
and Derived::Type
are technically different so there must be a conversion between the two.
We defined a couple of methods that we override in these child classes. These methods are staticly defined so that we can call this methods outside of an instance of these classes.
We privately define the map of types to strings that we will be using in our GetName
function. These are defined privately to prevent externally accessing and altering the values of our enum names. They are also defined as static
so that the values are initialized as soon as the class is utilized and does not require a specific instance of the class.
Other Benefits
There are a few other benefits of utilizing object oriented programming when implementing this new Enum
type.
Since these objects different classes, they are safe from accidental conversion between constants of different types. With integer constants, there is no safety when moving between constants of (what should be) different enumerations. The built-in enum
and enum class
types are type-safe between each other but come with their own issues. Enumerations defined with the enum
keyword cannot be converted between without casting. However, when using enum classes
, a variable cannot be used in combination with a resolution of an enumeration by default, for example:
Furthermore, neither of them can have member defined functions and must rely on globally scoped functions and operator definitions, which can cause namespace pollution. Namespace pollution can be an issue with enums
as they must be global in order to be used in many places. This also means that you cannot have two enums
with the same name globally. By encapsulating the similarly named enumerations into multiple different classes that represent the overarching data contained within the enumeration, we can limit the pollution of the namespace or potential collisions with other enumerations.
We can further expand on this concept of enumerations within a class to create multiple enumerations within one class. This may be useful for a ‘Defined Contants’ class, where there are multiple enumerated types contained within a single static
class so that they can all be included with a single include.
However, our Enum
types are implemented as classes and we can define member methods and operators. These member operators can be utilized to perform bitwise operations as if the enumerations encapsulated in the Enum
class are bit flags rather than exclusive values. This is similar to the FlagAttribute
attribute on the C# Enum
class.
More to come
There are still other features of this Enum
class that I haven’t mentioned here (operators, etc.) or will be developed later. If you would like to see the source and some example code for this project, you can check the repo for this post here, on GitHub. Feel free to create issues for features or use this source in your own projects.