x-macros are a great meta-programming idiom for C & C++, and I've been using them for years in my own code.
Here's an example that demonstrates why they are so useful.
An enum for dog breeds
Let's say that I want to enumerate dog breeds. I know the breeds I want to enumerate now, but I also want to be able to add more later. I also want to be able to convert the enum values to and from a human-readable string. The following demonstrates how x-macros can help make this easy.
using namespace std;
// 1. Define an expanding macro that encodes the enum values
// 2. Define a macro with syntax for a single enum value, then expand
;
string
Dog
int
Look closely at the macro EXPAND_ENUM_DOGS, that's the important part. See that it has an input parameter. That parameter is itself ANOTHER macro with its own input parameters.
It doesn't define anything on it's own, but can be used by its input (DOG, in this example) to do something useful. This is a form of Metaprogramming. Your pre-defined data elements are encoded within the macro, which becomes the source of truth any code you may want to generate.
Take a look at the to_string function:
string
This is the literal expansion of EXPAND_ENUM_DOGS(CASE)
case DOG_BEAGLE: return "beagle";
case DOG_DOBERMAN: return "doberman";
case DOG_PUG: return "pug";
case DOG_POODLE: return "poodle";
case DOG_TERRIER: return "terrier";
case DOG_BOXER: return "boxer";
And again, look at the to_dog function
Dog
The same macro EXPAND_ENUM_DOGS(CASE) expands differently, since it sees a new definition for CASE
if return Dog::DOG_BEAGLE;
if return Dog::DOG_DOBERMAN;
if return Dog::DOG_PUG;
if return Dog::DOG_POODLE;
if return Dog::DOG_TERRIER;
if return Dog::DOG_BOXER;
The key idea is to repeat against the execution of an expanding macro. This can then be leveraged to eliminate large repeating patterns in your code, as well as guarantee correctness.
Ever since I learned this technique, I have used it extensively in my own code. It makes working with enums in C++ much more functional than the base language allows. It can also be used to implement a light-weight form of polymorphism. The performance benefits are also pretty clear, this dog breed example could have been written with a lookup table, which would have incurred an additional memory and runtime cost.
If you want more practical examples of this trick, see my code library ut, which I actively maintain on github and uses x-macros throughout (hint: look specifically at color.hpp.