C++ Macro-defined Enums

I picked up a nifty trick recently for defining enumerations in C++ using some funky syntax that takes advantage of user-defined macros.

Let us recall how simple C++ macros work.

This will output a value of 5, as MYOPERATION is replaced by the preprocessor with its contents which adds the two variables.  Our MYOPERATION macro does not have to add, as we could define it to do whatever we wish. We also have the ability to change what our MYOPERATION macro means mid-file.

Elegance aside, this feature can help us leverage the preprocessor to do some nifty stuff for us.
Consider a situation in which we need an enum for different types of fruit. While having this enumeration is nice, we also need a way to output our fruit enum to the user.  A naive solution could be as so:

While this method certainly does work, it isn’t very expandable. If we add a new fruit to the enum, we must add a matching string to our array at the correct spot. Wouldn’t it be nice if we could somehow take our enumerated values and turn them all into strings? They are (for the most part) the same text after all.

Well good news everyone, we can! Using the fact that we can redefine macros, we can leverage the preprocessor to generate both the enum and the array of strings for us. It is trivial to create two separate macros that generate these for us as long as we define our data correctly.

The first step we need to do is define all of our fruit. This will have to be in its own file with nothing else in it:


Note – Resist the urge to use header guards
Good programming practices make us want to use a header guard in all of our headers, but if we want to use our enum declaration header for more than one place then we must leave it out!

On its own, this file looks quite wonky, but with preprocessor macros in-mind it will start to make sense. What we have done is called a macro called FRUIT on all of our tasty fruit (Note: We did not include our count in this). Now all we have to do is write the macros that will take in the data. These should be in a separate file from the declarations of our enum values:

This macro tells the preprocessor to append a command to our fruit names. Since they were all defined in a row in FruitDeclarations.h, this builds a nice enumeration for us. This is also a nice reminder that you can include header files in all sorts of cool places. We also need to add our count value onto the end of the list, as we did not add it in the declarations file.

By redefining the FRUIT macro we can generate our list of strings with the same data set:

If you are unfamiliar with preprocessor directives, the ‘#‘ character allows us to ‘stringify’ an argument of a macro. We use this to turn all of our fruit names into C-strings in our array. Also notice how we are adding an additional string on the end of the array. This allows us to handle the user passing our fruit count into the array.

Edit – Good practices with extern
A friend has reminded of me a best practice when it comes down to declaring/defining arrays in header files.  When defining an array in a header file, it is entirely possible for the compiler to instantiate said array in every module that includes our header.  To ensure we only instantiate our array once, we can use extern in our header to declare the array, and then define it in a .c/.cpp file.  The fix looks something like this:

Note: We must define the length of our array using our enum value ‘Count’ as the compiler doesn’t have our initializer list to deduce the length of the array. We must add one onto it for the additional ‘Invalid’ string, as the enumeration values are 0-based.

And there you have it. Using this method you can easily create all sorts of automatically generated data along-side enumerations in C/C++. Please let me know in the comments below if you find any more cool tricks you can do with this method!

Jake

I am a dedicated student developer who enjoys tools and game engine programming. I’d love to experience as many different aspects of the software industry as possible so I can find which part fits me best.

Leave a Reply

Your email address will not be published. Required fields are marked *