How to sync enums with their strings in c++

To keep your sanity (maybe)

Posted by 2nafish117 on January 26, 2021 · 5 mins read

Do you just want to define your enums once, and have their strings generated automatically? Well you can do that, if you're willing to fuck with the cpp preprocessor.

Lets first start with wtf im even talking about. Here is a code snippet.

// Days.h
enum Days {
	MONDAY = 0,
	TUESDAY,
	WEDNESDAY,
	THURSDAY,
	FRIDAY,
	SATURDAY,
	SUNDAY,
	NUM_DAYS
};

const char* const DaysToString[] = {
	"MONDAY",
	"TUESDAY",
	"WEDNESDAY",
	"THURSDAY",
	"FRIDAY",
	"SATURDAY",
	"SUNDAY",
	"INVALID_DAY"
};

// usage: DaysToString[FRIDAY] -> gives you "FRIDAY"

So whats the problem with this?

Well for starters if you need to add another day, then you need to make changes in the code in two places. Might not look too difficult when you have so few enums, but think about maintaining them when you have more than a handful. Or being the forgetful shit you are, you may just forget. Also, if you re-arrange the days, you need to re-arrange them in the strings as well. Change the enum spelling? Guess what, you need to change it in the strings.

A good programmer always finds new ways to keep his sanity (until that new solution comes to bite him in the ass). What am i talking about? Using the cpp preprocessor of course. Trade one evil for another.

The solution I provide is not my own, i just saw it from this video and wanted to document it in my blog.

First you make a file containing this ...

// DaysEnums.incl, call it whatever tf you want.
REGISTER_ENUM(MONDAY = 0) // You can do the "= 0" as well
REGISTER_ENUM(TUESDAY)
REGISTER_ENUM(WEDNESDAY)
REGISTER_ENUM(THURSDAY)
REGISTER_ENUM(FRIDAY)
REGISTER_ENUM(SATURDAY)
REGISTER_ENUM(SUNDAY)

Dont lose your shit yet, next code snippet actually defines REGISTER_ENUM. Notice it contains no commas, semicolons.

And in a second file, this ..

// DaysEnumsDef.h, call it whatever tf you want.
#define REGISTER_ENUM(x) x, // It expands to literally x followed by a comma

enum Days{
	#include "DaysEnums.incl" // Does a literal copy paste into here.
	NUM_DAYS // You dont want to register this in the DaysEnums.h file.
};

#undef REGISTER_ENUM // Redundant i guess.
#define REGISTER_ENUM(x) #x, // Preprocessor wizardry replaces #x with the string version of x, literally.

const char* const DaysToString[] = {
	#include "DaysEnums.incl"
	"INVALID_DAY" // Pretty sure you know why.
};

#undef REGISTER_ENUM // Undefine it to not pollute the codebase.

TLDR: basically we are redefining the macro REGISTER_ENUM to first list out the enum definitions, and then redefining it to list out the same "word" within doubleqotes. This allows you to just list the enums once and let the preprocessor make sure that the strings and the enum is being defined in the same order, Macro magic.

Now its your choice if you want to use this or not. Is it really simpler than just doing it yourself? You be the judge of that. Preprocessor magic shit like this is what makes newcomers to your codebase bleed their eyes out, so maybe comment this shit out? And with that ill see y'all in the next post.