ishani-logo
December 5th, 2012  »  Programming  »  No Comments

C++ enums are pretty boring, right? Depends. Can yours do this?

VertexFormat::Enum k = VertexFormat::Vertex_Pos3_RGBA_Normal3_UV0;
const char* str = VertexFormat::toString(k);
k = VertexFormat::fromString(str);

printf("%s has %i entries, of which %s == %i\n\n", 
  VertexFormat::enumName(),
  VertexFormat::enumCount(),
  str,
  k);

// prints: "VertexFormat has 7 entries, of which Vertex_Pos3_RGBA_Normal3_UV0 == 5"

Or how about

for (VertexFormat::StorageType i=0; i<VertexFormat::enumCount(); i++)
{
  printf("> %s \n", VertexFormat::toString( VertexFormat::getByValue(i) ) );
}

I can convert to and from a string representation, get proper name isolation as well as basic reflection. Defining the enum is not much more work than normal:

#define BF_VERTEX_FORMATS(_entry) \
  _entry(Vertex_Pos3) \
  _entry(Vertex_Pos4) \
  _entry(Vertex_Pos3_RGBA) \
  _entry(Vertex_Pos4_RGBA) \
  _entry(Vertex_Pos3_RGBA_Normal3) \
  _entry(Vertex_Pos3_RGBA_Normal3_UV0) \
  _entry(Vertex_Pos3_RGBA_UV0) 

CREATE_FANCYENUM(VertexFormat, uint32_t, BF_VERTEX_FORMATS);

Using the marvels of layered high-order macros – Hey, did you know you can pass macros to macros (to macros)? because you can. And it’s awesome. – we get something a bit more useful than a weakly-typed alias.

I’m not a massive fan of writing my own ‘meta-language’ using macros as it ends up being a nightmare to debug should it go wrong or refuse to compile – but in this case the underlying code is simple to keep track of and provides such a useful addition that it’s worth dealing with any negatives.

Here’s how it works…

First up it wraps my preferred way to define enums these days – inside a struct. Doing this means we end up with more specific and isolated naming, similar to the new enum class functionality in C++11 but with the ability to add our helper functions inside the same namespace, plus it works on non-C++11 compilers.

struct PlayerState
{
  enum Enum
  {
    Alive,
    Dying,
    Dead,
    Count
  }
};

PlayerState::Enum ps = PlayerState::Alive;

// instead of 

enum PlayerState
{
  psAlive,
  psDying,
  psDead,
  psCount
};

PlayerState ps = psAlive;

We can have identical enum names (establishing common entries, like Count) separated by the enclosing type. We’re basically aping C#, and why not. See Noel @ GamesFromWithin for more reasons.

A top-level macro allows for a few options, including the ability to generate fancy enums that have manually-specified values:

#define BF_WITH_IDS(_entry) \
  _entry(Kepler,   5) \
  _entry(Hthran,   10) \
  _entry(Eidelon,  11) \
  _entry(Montegue, 20) \
  _entry(Falagray, 22) \
  _entry(Winteray, 60) \
  _entry(Sepfarra, 51) 

CREATE_FANCYENUM_IDS(Codenames, int32_t, BF_WITH_IDS);

The code to generate fancy enums is hosted as a gist right here. It’s all of 120 lines long so any verbose explanation is probably redundant.

Compiled with MSVC 2010 and 2012, as well as Clang 3.3, on the highest warning levels. If you do use it, please let me know improvements or fixes!

It’s very easy to modify the code to use the aforementioned enum class feature for even stronger typing if you fancy, although you will end up having to type PlayerState::Enum ps = PlayerState::Enum::Alive; as you can’t inherit from a enum class so we can’t combine the types together into a single entity.

Add Comments...

Leave a Reply

Your email address will not be published. Required fields are marked *