in C++

Stupid C++ Tricks #2: Better Enums

So much for the new year’s resolution to write some sort of an update every week. That went out the window pretty quickly. Especially now that I’ve taken over the Inner Product column for Game Developer Magazine and that’s taking away some of my writing time (check out the May issue for my first column!).

It turns out that Charles’ old article Stupid C++ Tricks: Adventures in Assert is one of our most viewed entries, even after all this time. So I figured I’d follow it up with a really, really simple C++ trick. It’s almost trivial, really, but I’ve totally fallen in love with it. At first, when Charles introduced me to it, I was kind of lukewarm. But now I’m finding myself going through refactoring rampages in the code changing things to be this way. Intrigued? Read on.
C++ enums are great in practice, but they’re a bit lacking once you really start flexing them. Don’t get me wrong, they’re definitely a step up from #defines, but you can’t help but wish for more. Type safety is only so-so (enums get promoted to ints silently), you can’t forward declare them so you’re forced to have extra includes, you can’t split up a declaration of enums across multiple files (the compiler would have to do more work, but it would be awesome to be able to create uids for different classes in their own header files instead of a global one). But the most grating of design decisions is their scoping rules.

Take for example a set of enums describing the game flow:

enum GameFlowType
{
    Run,
    Exit,
    Restart,
    Restore,
};

Now functions can return a GameFlowType to indicate that the main loop should do. So far so good.

Except that, look at how it’s used:

GameFlowType DoMainLoop(bool render)
{
    //...
    if (someCondition)
        return Exit;
    return Run;
}

Ouch! We’ve managed to pollute the global namespace with totally generic keywords such as Run and Exit. What if I want to have an enum that controls what action an AI is going to take:

enum AIAction
{
    Enter,
    Exit,
    Stop,
    Walk,
    Run,
};

That won’t even compile because the symbols conflict with the GameFlowType ones. Even worse, it will compile just fine if they don’t happen to be included in the same compilation unit. So you might be fine all along until you write some AI code that has control over the game flow. Oops!

OK, so you can prefix all your enums like this:

enum GameFlowType
{
    GameFlowTypeRun,
    GameFlowTypeExit,
    GameFlowTypeRestart,
    GameFlowTypeRestore,
};

and

enum AIAction{
    AIActionEnter,
    AIActionExit,
    AIActionStop,
    AIActionWalk,
    AIActionRun,
};

That will technically fix the problem, but it’s a bit ugly, and we’re still polluting the global namespace (what if I wanted to have a class called AIActionEnter?).

Another potential solution is to score the enum inside the class that is using them. So we could have:

class AIAction{
public:
    enum AIActionType
    {
        Enter,
        Exit,
        Stop,
        Walk,
        Run,
    };
};

Much cleaner, do doubt, but also with its share of problems: First of all, it forces you to associate a set of enums with a class, which is not something you always want to do. But the biggest problem is that as soon as you have more than one set of enums per class, they all get promoted to the same scope:

class AIAction
{
public:
    enum AIActionBehavior
    {
        Enter,
        Exit,
        Stop,
        Walk,
        Run,
    };

    enum AIActionGroup
    {
        None,
        Self,
        Team,
        All,
    };
};

When we see one of those enums in code referred to as AIAction::Self, we really have no idea that it’s referring to that group the action is applied to, as opposed to AIAction::Enter, which is the type of action.

Our solution? Move all enums into a descriptive namespace.

namespace GameFlowType
{
    enum Enum
    {
        Invalid,
        Run,
        Exit,
        Restart,
        Restore,
    };
}

When you have a variable of that enum type, it’s listed as GameFlowType::Enum type, which clearly indicates what it does. And its values are referred to as GameFLowType::Exit. You get the ideal scoping, you’re not tied to any particular class, things are explicit but not verbose, you’re no polluting the global namespace, and it’s doesn’t affect the runtime or compilation times.

Sometimes the best solutions are the simplest ones.

  • http://games.greggman.com greggman

    I do this. I know some of you won’t like it but the pragmatic ones will.

    #undef GAMEFLOWTYPE_OP
    #define GAMEFLOWTYPES \
    GAMEFLOWTYPE_OP(Invalid)
    GAMEFLOWTYPE_OP(Run)
    GAMEFLOWTYPE_OP(Exit)
    GAMEFLOWTYPE_OP(Restart)
    GAMEFLOWTYPE_OP(Restore)

    That allows me to do things like.

    #undef GAMEFLOWTYPE_OP
    #define GAMEFLOWTYPE_OP(name) name,

    namespace GameFlowType { enum Enum {
    GAMEFLOWTYPES
    }; }

    const char* GetGameFlowTypeName(GameFlowType::Enum type) {
    #undef GAMEFLOWTYPE_OP
    #define GAMEFLOWTYPE_OP(name) #name,
    static const char* names[] = {
    GAMEFLOWTYPES,
    };
    return names[type];
    }

    Etc. Things never get out of sync and I don’t need some extra tools added to my build that will cause other people pain when they attempt to integrate. It’s 100% standard C/C++.

    More examples:

    // predeclare functions to handle each game flow type
    #undef GAMEFLOWTYPE_OP
    #define GAMEFLOWTYPE_OP(name) void #name ## __handler(GameState& gameState);

    GAMEFLOWTYPES;

    // Make a function dispatacher based on the game flow type
    typedef void (*GameFlowFuncPtr)(GameState& gameState);

    void CallGameFlowFunction(GameFlowType::Enum type, GameState& gameState) {
    #undef GAMEFLOWTYPE_OP
    #define GAMEFLOWTYPE_OP(name) #name ## __handler,
    static GameFlowFuncPtr funcTable[] = {
    GAMEFLOWTYEPS
    };
    funcTable[type](gameState);
    }

    All that’s left is to define the actual functions. If I want to add a new one it’s a 1 line change.

  • http://www.gamesfromwithin.com Noel

    Ah yes, we call those fruit lists around here. I kind of hate them, but I accept them (and use them) to do wat you suggested there and generate strings and enums. Anything more than that, and it usually becomes a horrible nightmare of un-debuggable code.

  • http://games.greggman.com greggman

    They are really only useful for low-level small snippets of code that should only have to be debuggged once and then left alone (like the function dispatcher above). They certainly are not useful for anything much bigger. But, being hard to debug, since they should only have to be debugged once or twice, is a not so expensive a price to pay vs the odds of causing an error by not using them (ie, things getting out of sync) and by the productivity benefits they supply. In the example above, without them, 4 places in the code would have to edited for each thing added vs with them only 1 place has to be edited. For dispatch code like that above that’s a big win.

  • JoeProgrammer

    what about doing this?

    typedef enum _eSampleEnum {
    E_SE_ONE,
    E_SE_TWO,

    E_SE_MAX
    } eSampleEnum;

    Don’t you get better type checking this way and doesn’t it minimize naming collisions?

    I got in the habit of typedef’ing all my enums. But under the covers of CPP, I’m not clear on all the advantages or disadvantages of doing so.

  • http://gamecreator.myopenid.com/ SasQ

    But now you have `GameFlowType::Enum` for the type name instead of simply `GameFlowType`. And yes, I hate it too. It’d be better if C++ use the same scoping rules for enums as for other namespaces & classes, and simply use `GameFlowType::Exit` by default, without the need for use of some tricks.

  • http://www.oogtech.org/content Tea

    I’m surprised to see developers using this wonderful hack (cough cough) not only in place of, but also in conjunction with, less gratuitous uses of namespaces.
    Clarification: does a developer who wisely scope within com::acme::modular need com:acme::modular::escape_from_the_global_namespace::Enum?

    What am I missing?

    • crosswalking

      You cannot have namespaces within a class. This is where the trick is most useful.

  • http://nmmmnu.blogspot.com nmmm

    very nice article, but you need to write C++11 update for enum class

    • llopis

      C11 fixes everything and makes this completely irrelevant. Even without enum class, you can finally refer to the enum values with the enum prefix in front (GameFlowType::Exit). So yay! :-)

      • http://nmmmnu.blogspot.com nmmm

        yes, but I am not sure what happen if you have same “value” in C-enum.