50Ply Blog

Building Things

C++: Eliminate Parallel Lists With Higher Order Macros

| Comments

Here’s a handy tool in my tool-belt that I thought would be worth sharing. We’ve all dealt with situations in development where we have to create multiple lists of the same logical things. Here’s a classic example:

1
2
3
4
5
6
7
8
9
10
11
enum Animals {
  DOG,
  CAT,
  MOUSE
};

const char* AnimalNames[] = {
  "DOG",
  "CAT",
  "MOUSE"
};

Here we’ve defined an enumeration and their stringified names so that we can pretty-print them. But, this means we have two lists that must have the same number and order of elements or there is a bug. These are parallel lists and they can be tricky to maintain.

Luckily, the C preprocessor gives us an elegant (but not well known) solution. We can put the list into a macro and then generate the parallel lists in the preprocessor such that they will always be correct. Here’s how it works:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define ANIMAL_LIST(m)        \
  m(DOG),                     \
  m(CAT),                     \
  m(MOUSE)

#define GENERATE_ENUM(m) m

#define GENERATE_STRING(m) #m

enum Animals {
   ANIMAL_LIST(GENERATE_ENUM)
};

const char* AnimalNames[] = {
   ANIMAL_LIST(GENERATE_STRING)
};

Slick, huh? We’ve only listed our items once but we’ve generated the enum and the stringified names from that single list. Here’s what happened:

We defined a macro named “ANIMAL_LIST” that takes another macro as an argument. The macro then calls that macro with each of the elements of the list. Thus, ANIMAL_LIST is a “higher order macro” because its behavior is partially determined by the macro it accepts as an argument. We then define our enum and within the curly braces we expand the ANIMAL_LIST higher order macro using a macro that just emits each of the elements of the list directly. We then define our stringification array and within those curly braces we expand ANIMAL_LIST using a macro that emits the stringified version of each list element.

For a far more ambitious use of this higher-order macro concept, check out the BrianScheme Virtual Machine. Here, the opcode_table is a higher order macro that is expanded 7 times to produce opcode to string mappings, symbols for each of the opcodes, pointer to opcode mappings, and more. That’s a lot of parallel lists that must be kept consistent!

Comments