Clean enumerations using xmacros

Introduction

In this short article, we will discuss a classical C++ engineering problem: storing meta data alongside enumeration values (such as their string equivalent). The solution we will end up with is based on so called "xmacros", a not so well known C-inherited trick.

The problem

Let's consider a simple case: You have to store some enumerations representing colors. Something of the kind:

// --- color.h
enum Color
{
  RED,
  GREEN,
  BLUE,
  PURPLE,
  YELLOW,
  _COLOR_MAX
};

One day or another, you will have to dump these enumerations, either for debugging or logging purpose. A typical solution would be to store their string equivalent in an other container, and remember to update it accordingly when you add a new color.

// --- color.h
#include <string>

enum Color
{
  RED,
  GREEN,
  BLUE,
  PURPLE,
  YELLOW,
  _COLOR_MAX
};

std::string colorName( Color c );

// --- color.cpp
#include "color.h"

#include <cassert>

static const std::string colorNames[] =
{
  "RED",
  "GREEN",
  "BLUE",
  "PURPLE",
  "YELLOW"
}

std::string colorName( Color c )
{
  assert( c<_COLOR_MAX );
  return colorNames[c%_COLOR_MAX];
}

Maintaining these meta data is a pain. You data is spread in multiple parts, and one could easily forget to maintain either one of these containers.

X macros

The whole idea behind X macros is to store the enumerations in macros instead of directly defining enumerations. Consider the following example:

enum Color
{
#define X(code) code,
  X( RED    )
  X( GREEN  )
  X( BLUE   )
  X( PURPLE )
#undef X
};

Going one step further, we could extract the `X(...)̀ declarations in a clean macro list, outside the enumerations scope.

#define COLOR_LIST \
  X( RED    )      \
  X( GREEN  )      \
  X( BLUE   )      \
  X( PURPLE )

enum Color
{
#define X(code) code,
  COLOR_LIST
#undef X
};

You should start to understand what we are doing here. By exporting the color list outside of the enumeration scope, we now have the possibility to declare a similar container for color string equivalents!

#define COLOR_LIST \
  X( RED    )      \
  X( GREEN  )      \
  X( BLUE   )      \
  X( PURPLE )

enum Color
{
#define X(code) code,
  COLOR_LIST
#undef X
};

static const std::string colorNames[] =
{
#define X(code) #code,
  COLOR_LIST
#undef X
};

Possible improvement: definition files

The C++ standard does not allow arbitrary length lines to be parsed. In fact, compliant compilers are only required to be able to parse at least 4k characters. This directly implies that we will not be able to store very long enumerations in the same list macro. Most of the time, this problem is solved by exporting all xmacros in a separate .def file that you can later include where you need it:

// --- color.def
X( RED    )
X( BLUE   )
X( GREEN  )
X( PURPLE )
X( YELLOW )

// --- color.h
enum Color
{
#define X(code) code,
#  include "color.def"
#endif
};

Possible improvement: multiple meta data

Keep in mind that you do not need to deduce all needed meta data from a single xmacro argument, you can store whatever you need, and tweak the xmacro definition to your likings:

// --- color.def
X( RED,    "Red",   0xFF0000 )
X( GREEN,  "Green", 0x00FF00 )
X( BLUE,   "Blue",  0x0000FF )

// --- color.h
enum Color
{
#define X(code,name,mask) code,
#  include "color.def"
#endif
};

static const std::string colorNames[] =
{
#define X(code,name,mask) name,
#  include "color.def"
#undef X
};

static unsigned int colorMasks[] =
{
#define X(code,name,mask) mask,
#  include "color.def"
#undef X
};

A complete example

Here is a complete example of the use of xmacros. We use the NARGS trick (https://groups.google.com/d/msg/comp.std.c/d-6Mj5Lko_s/5R6bMWTEbzQJ) to retrieve the number of colors without having to pollute the Color enumeration with _MAX_COLORS.

// --- color.h
#ifndef COLOR_H
#define COLOR_H

#include <string>

#define COLOR_LIST \
  X( RED    )      \
  X( GREEN  )      \
  X( BLUE   )      \
  X( PURPLE )      \
  X( ORANGE )      \
  X( YELLOW )

enum Color
{
#define X(code) code,
  COLOR_LIST
#undef X
};

/**
 * Returns the string equivalent of a color code
 */
std::string colorName( Color c );

/**
 * Returns true if c maps to a valid color code, false otherwise
 */
bool colorValid( unsigned int c );

/**
 * Returns the total count of existing color codes
 */
std::size_t colorCount();

#endif // COLOR_H

// --- color.cpp
#include "color.h"

#include <cassert>

#define NARG(...)  NARG_(__VA_ARGS__,RSEQ_N())
#define NARG_(...) ARG_N(__VA_ARGS__)
#define ARG_N(                             \
   _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
  _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
  _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
  _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
  _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
  _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
  _61,_62,_63,N,...) N

#define RSEQ_N()                \
 63,62,61,60,                   \
 59,58,57,56,55,54,53,52,51,50, \
 49,48,47,46,45,44,43,42,41,40, \
 39,38,37,36,35,34,33,32,31,30, \
 29,28,27,26,25,24,23,22,21,20, \
 19,18,17,16,15,14,13,12,11,10, \
  9, 8, 7, 6, 5, 4, 3, 2, 1, 0

static const std::string colorNames[] =
{
#define X(code) #code,
  COLOR_LIST
#undef X
};

std::string colorName( Color c )
{
  assert( c<colorCount() );
  return colorNames[c%colorCount()];
}

bool colorValid( unsigned int c )
{
  return c<colorCount();
}

std::size_t colorCount()
{
#define X(code) code,
  return NARG(COLOR_LIST)-1;
#undef X
}

// --- main.cpp
#include <iostream>
#include <cstdlib>

#include "color.h"

int main( int argc, char** argv )
{
  std::cout << "Red   code is " << RED   << std::endl;
  std::cout << "Green code is " << GREEN << std::endl;
  std::cout << "Blue  code is " << BLUE  << std::endl;
  std::cout << std::endl;
  std::cout << "Red   name is " << colorName(RED  ) << std::endl;
  std::cout << "Green name is " << colorName(GREEN) << std::endl;
  std::cout << "Blue  name is " << colorName(BLUE ) << std::endl;
  std::cout << std::endl;
  std::cout << "Code 0 is valid? " << colorValid(0) << std::endl;
  std::cout << "Code 5 is valid? " << colorValid(5) << std::endl;
  std::cout << "Code 6 is valid? " << colorValid(6) << std::endl;
  std::cout << std::endl;
  std::cout << "Total color count: " << colorCount() << std::endl;
  
  return EXIT_SUCCESS;
}

Boost.PreProcessor

An other possibility would be to use Boost.PreProcessor (http://www.boost.org/doc/libs/release/libs/preprocessor/doc/index.html). An advantage of this alternative is that you have a full featured macro framework to manipulate your enumerations. Here is a quick sample of what you may end up with:

// --- color.h
#include <string>

#include <boost/preprocessor/seq/enum.hpp>

#define COLOR_LIST \
  (RED   )         \
  (GREEN )         \
  (BLUE  )         \
  (PURPLE)         \
  (YELLOW)         \
  (ORANGE)

enum Color
{
BOOST_PP_SEQ_ENUM(COLOR_LIST)
};

std::string colorName ( Color        c );
bool        colorValid( unsigned int c );
std::size_t colorCount();

// --- color.cpp
#include <cassert>

#include <boost/preprocessor/seq/elem.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/seq/transform.hpp>
#include <boost/preprocessor/stringize.hpp>

static const std::string colorNames[] =
{
#define OP(s,data,element) BOOST_PP_STRINGIZE(element)
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(OP, 0, COLOR_LIST))
#undef OP
};

std::string colorName( Color c )
{
  assert( colorValid(c) );
  return colorNames[c%colorCount()];
}

bool colorValid( unsigned int c )
{
  return c<colorCount();
}

std::size_t colorCount()
{
  return BOOST_PP_SEQ_SIZE(COLOR_LIST);
}

// --- main.cpp
#include <iostream>
#include <cstdlib>

int main( int argc, char** argv )
{
  std::cout << "Red   code is " << RED   << std::endl;
  std::cout << "Green code is " << GREEN << std::endl;
  std::cout << "Blue  code is " << BLUE  << std::endl;
  std::cout << std::endl;
  std::cout << "Red   name is " << colorName(RED  ) << std::endl;
  std::cout << "Green name is " << colorName(GREEN) << std::endl;
  std::cout << "Blue  name is " << colorName(BLUE ) << std::endl;
  std::cout << std::endl;
  std::cout << "Code 0 is valid? " << colorValid(0) << std::endl;
  std::cout << "Code 5 is valid? " << colorValid(5) << std::endl;
  std::cout << "Code 6 is valid? " << colorValid(6) << std::endl;
  std::cout << std::endl;
  std::cout << "Total color count: " << colorCount() << std::endl;

  return EXIT_SUCCESS;
}