I'm Morgan McGuire (@CasualEffects). I've been working on computer graphics and games for 20 years at great places including NVIDIA, Williams College, Brown University, and Activision.

See my home page for a full index of my blog posts, books, research, and projects.

Tuesday, August 13, 2013

A Really Smart Enum in C++

For the G3D Innovation Engine, I wanted much richer enumerated type functionality than C++ enum (or even recent extensions) provide, without the template error nightmares and complexity of something like the Boost library's enums or X macros. I also did not want to use non-standard compiler extensions or an offline code generator; portability and minimal maintenance are important for my multi-platform code and small development team. I call my solution "really smart enums" because they extend the so-called C++ Intelligent Enum design pattern.  The solution that I used allows all of the following:

// 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::vector 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;
}
I'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:
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.