// Declare my enum G3D_DECLARE_ENUM_CLASS(Day, SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY); // Scoped names Day d = Day::TUESDAY; // Convert to/from std::string and char* Day d2("SATURDAY"); printf("%s = %d\n", d.toString(), d.value); // Store (and retrieve from) into a G3D dynamically typed variable Any a(d); d = a;The macro functions by defining a class that uses a combination of overloading and runtime search methods. The real key is a trick that it uses a variadic macro, a runtime parser, and the C++ stringizing operator to generate both the underlying C/C++ enum and an array of strings of the names. All other methods that interact with strings on the class eventually pass through a single
toString
method that looks like this:
static const char* toString(int i, Value& v) {\ static const char** str = G3D::_internal::smartEnumParseNames(#__VA_ARGS__);\ static const Value val[] = {__VA_ARGS__};\ const char* s = str[i];\ if (s) { v = val[i]; }\ return s;\ }\
The parser function is (see the links at the bottom for the full code usable as a header and source file):
#include <string.h> #include <vector> #if defined(_WIN32) || defined(_WIN64) # define strtok_r strtok_s #endif const char** smartEnumParseNames(const char* enumValList) { char *saveptr = NULL; // We'll intentionally mutate (via strtok) and then leak the memory allocated here so that it // can be referenced by a static local variable inside the enum class toString method. We // could register an atexit handler to clear the allocated memory for all enums at once on shutdown // if this bothered us. char* copy = strdup(enumValList); std::vectorI've been working in both C++ and JavaScript lately and often need them to interoperate. Another G3D helper function for the smart enum generates a JavaScript enum that has identical values to the C++ one:ptrs; const char* DELIMITERS = " ,"; // Tokenize for (char* token = strtok_r(copy, DELIMITERS, &saveptr); token; token = strtok_r(NULL, DELIMITERS, &saveptr)) { ptrs.push_back(token); } // Allocate one extra element for the NULL terminator. Note that array will also be intentionally // leaked. char** array = (char**)::malloc(sizeof(char*) * (ptrs.size() + 1)); ::memcpy(array, &ptrs[0], sizeof(char*) * ptrs.size()); array[ptrs.size()] = NULL; return (const char**)array; }
TextOutput t("Day.js"); enumToJavaScriptDeclaration<Day>(t); t.commit();The resulting JavaScript code produces an enum that can be used like:
var d = Day.SUNDAY;The JavaScript values are even read-only--accidentally attempting to assign
Day.MONDAY = 3
produces an error, as it should. You could imagine doing something similar for Python, Java, or another language.
The only limitation is that one can't assign explicit values to the enums, e.g.,
G3D_DECLARE_ENUM_CLASS(Day, MONDAY = 2, ...)
. The runtime parser has no problem with this, but the use as tokens inside toString
would fail. The code intentionally leaks a small amount of memory, which might alarm a leak checker. As noted in the comments of my implementation, an atexit
handler can be registered to avoid this false alarm.
The full source code for the class is available as open source under the BSD license from:
http://sourceforge.net/p/g3d/code/3081/tree/G3D9/G3D.lib/include/G3D/enumclass.h
http://sourceforge.net/p/g3d/code/3081/tree/G3D9/G3D.lib/source/enumclass.cpp
Morgan McGuire is a professor of Computer Science at Williams College and a professional game developer. He is the author of The Graphics Codex, an essential reference for computer graphics that runs on iPhone, iPad, and iPod Touch.