I really like x-macros, and over the past few years I've been using them more and more in my code.
Here's an example that demonstrates why they are useful:
Example - Enums
Let's say that I want to enumerate dog breeds. I know which ones I want now, but I'll need to be able to add more later. I'd like to be able to convert the enum value to and from a human-readable string. This is how to do all of that using x-macros.
using namespace std;
;
string
Dog
int
Pay attention to the pre-processor macro EXPAND_ENUM_DOGS, that's the important part. It has an input parameter. That parameter is itself ANOTHER pre-processor 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.
To demonstrate, look closely 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 a pre-processor macro. This ability 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. I have also used it 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.