Enum names as strings (C/C++)

__/~\_/~\__
 »»»»»»»»»»»»»»»          (== | ^ ==)

Introduction

This is a common problem that many developers in C/C++ come across. Although enum's and #define'd values are functionally the same, the former is preferred since the value would be displayed as the enum-name during debugging in contrast to just the value as with the latter. Sometimes it is required for the programmer to generate textual dumps of the internal state of the system. This and other such cases require being able to convert from an enum-value into it enum-name.

Unfortunately C++ does not innately provide any support for displaying the enum-name associated with the enum-value unlike C# and similar languages which at least facilitate indirect means to do so, although not highly efficient. I always wished it were possible to just perform a static_cast of an enum value to a string ;). But since C++ does not permit operator overloading at global scope, this didn't quite seem possible.

I finally gave in to manually generating a table of strings, where the index of each string (enum-name) corresponds to the enum-value of the enum-name the string represents. Alternately, if the enum-values are non-sequential the std::map[1] is used to associate enum-values with their corresponding enum-names. The following e.g. illustrates this with examples.

enum DayOfWeek
{
sunday,
monday,
tuesday,
wednesday,
thursday,
friday,
saturday
};

static const char * const DayOfWeek2Str[] =
{
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday"
};

int main()
{
enum DayOfWeek sun = sunday;
cout<<DayOfWeek2Str[sun];

return 0;
}

#include <iostream>
#include <boost/assign/list_of.hpp> // for 'map_list_of()'
#include <boost/assert.hpp>
#include <map>
using namespace std;
using namespace boost::assign; // bring 'map_list_of()' into scope

enum DayOfWeek
{
sunday,
monday,
tuesday,
wednesday,
thursday,
friday,
saturday
};

map<DayOfWeek,const char*> DayOfWeek2Str = map_list_of
(sunday, "sunday")
(monday, "monday")
(tuesday, "tuesday")
(wednesday, "wednesday")
(thursday, "thursday")
(friday, "friday")
(saturday, "saturday")
;

int main()
{
enum DayOfWeek sun = sunday;
cout<<DayOfWeek2Str[sun];

return 0;
}

Ain't this good enough?

This does work in small applications, although it quickly begins to break in larger systems. The problem arises when such a code hits into production and later into maintenance phase. It is quite possible that during modification of the enum definition, the engineer forgets to update the mapping table appropriately. This can potentially lead to bugs which could take quite a lot of time to isolate in the misbehaving system. Since the definition of the enum clearly maps to the definition and initialization of the mapping (irrespective of it being an array or std::map), auto-generation of the enum-value to enum-name mapping can prove to be of great value in the long run. This is especially true as the number of enum's in the system increase or their definitions are subject to frequent modification.

Having faced with this problem often, I decided to write a utility that would perform this auto-generation from C/C++ source file. Although I was tempted to write a C/C++ program to to this, I decided against it considering that the system had only enum's whose values ran sequential. A C/C++ program to parse it was an overkill (considering the development & testing effort it would entail). A system where there is a mix of sequentially and non-sequentially numbered enum's might probably require an intelligent tool which can decide what to emit based on such characteristics. Coming to think of it, it would be much easier to define only one kind of enum's in a single source file, thus enabling use of two simple tools that handles them differently.

Now, assuming that we only have to handle either of sequential or non-sequentially numbered enum's only in any given C/C++ source file, AWK scripts seem to fit into the requirement just perfectly. It is quite easy to code (without having to write much to parse / match patterns) and more importantly it is simple enough for someone to change it without grave consequences. Since the system I happen to be working on had enum's whose values ran sequentially, I choose to use array of string over std::map for performance reasons. Note: Using std::map would be a more generic solution irrespective of nature of sequencing of enum values.

The AWK script which when run on a C/C++ file that generates a table of strings is available here awkenum.awk. It ain't rocket science to modify this to generate the output of the second kind shown above or to suit a template of you choice ;).

Conclusion

Enumeration value to string conversion is a recurring problem in systems implemented in C/C++. Auto-generation of the mapping from enumeration value to the corresponding enumeration name although a bit of an infrastructure to set up can prove to be highly effective and also relieves the programmer from having to diligently update the mapping correctly. All that the user needs to do is to setup the build process to invoke the AWK scripts on the appropriate files on a Pre-Build event.

This auto-generation can be much easily accomplished using scripting languages such as AWK when compared to a tool written in C/C++. Although when a more robust solution is required (which is seldom the case), it might probably be less appropriate to use AWK. Since if the input were incorrect the code would not compile anyway, diagnosis on improper input is usually unnecessary. As such the use of an AWK script as above provides an easy means to generate and update.