4

Imagine I have an X Macro for a list of items defined something like this:

#define X_MACRO(FN) \
  FN(foo) \
  FN(bar) \
  FN(zip)

This works great and I can call it to generate the same code templatized for each element, like:

#define xstr(s) str(s)
#define str(s) #s
#define PRINT_X(E) void print_ ## E () { std::cout << str(E); }; 
X_MACRO(PRINT_X)

This generates functions like void print_foo() { std::cout << "foo"; }; for each of the X_MACRO elements. So far, so good.

Now, however, I want the list of X Macro elements to be conditional on a pre-processor macro. For example the zip element should only be included in the X Macro if USE_ZIP is defined. Of course, I can't put an #ifdef inside the X Macro, like:

#define X_MACRO(FN) \
  FN(foo) \
  FN(bar) \
#ifdef USE_ZIP
  FN(zip)
#endif

I could instead write the list twice, once with zip and once without, based on the USE_ZIP like so:

#ifdef USE_ZIP
#define X_MACRO(FN) \
  FN(foo) \
  FN(bar) \
  FN(zip)
#else
#define X_MACRO(FN) \
  FN(foo) \
  FN(bar)
#endif

... but this violates DRY and more importantly it rapidly spirals out of control if you need to conditionally include other elements, which would require a list for each possible combination of USE_* macros.

How can I do this in a reasonable way?

BeeOnRope
  • 60,350
  • 16
  • 207
  • 386
  • 1
    The only way I know of is to factor it out into multiple, smaller macros (like building blocks), and then combine those. It violates DRY much *less*, but still isn't perfect. Then again, you aren't going to see a bunch of macros recommended in clean code anyway. :-) – Cody Gray - on strike Sep 03 '17 at 00:58
  • Well about "clean code" - it's a matter of picking your poison, right? I haven't found a universal replacement for x macros in C++, so when they apply they may very well be the cleanest alternative without going outside the language. @CodyGray – BeeOnRope Sep 03 '17 at 01:01

3 Answers3

8

One way to do this is to split things in a base style and call it from a super macro (I don't know if these have special names):

#define X_MACRO_BASE(fn) \
    fn(foo) \
    fn(bar) \

#if USE_ZIP

#define X_MACRO(fn) \
    X_MACRO_BASE(fn) \
    fn(zip)

#else

#define X_MACRO(fn) \
    X_MACRO_BASE(fn)

#endif

It's not perfect, but it still might be useful :-)


Another neat trick is to have a simple conditional macro (say if USE_ZIP was 0 or 1):

#define IF(cond, foo) IF_IMPL(cond, foo)
#define IF_IMPL(cond, foo) IF_ ## cond (foo)
#define IF_0(foo)
#define IF_1(foo) foo

Then you can say:

#define X_MACRO(fn) \
    fn(foo) \
    fn(bar) \
    IF(USE_ZIP, fn(zip))
Travis Gockel
  • 26,877
  • 14
  • 89
  • 116
2

There are a lot of different approaches to this.

The first one that comes to mind is, instead of one X macro, have several X macros that are conditionally defined. Then have a master X macro that calls all of them. This might still give you reasonably clear code. However, it may scale up badly if the number of conditions is large.

The second one that comes to mind is, don't use X macros, use Boost::preprocessor instead. There are good things and bad things about this, here's an example of what it might look like:

#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/control/if.hpp>
#include <boost/preprocessor/seq/for_each.hpp>

#include <iostream>

#define CONDITION 0
// #define CONDITION 1

#define MY_FIRST_SEQ (foo)(bar)(baz)
#define MY_SECOND_SEQ BOOST_PP_IF(CONDITION, (zip), (quz))
#define MY_SEQ MY_FIRST_SEQ MY_SECOND_SEQ


// Now we adapt original example
// The parameters r, data need to be there but don't have to be used, see here:
// http://www.boost.org/doc/libs/1_62_0/libs/preprocessor/doc/ref/seq_for_each.html
#define STR(x) #x
#define PRINT_X(r, data, E) void BOOST_PP_CAT(print_, E) () { std::cout << STR(E); };


// Now do this where we would invoke the X_MACRO
BOOST_PP_SEQ_FOR_EACH(PRINT_X, _, MY_SEQ)

int main() {}
Chris Beck
  • 15,614
  • 4
  • 51
  • 87
  • 1
    I think if you're going to suggest using the boost preprocessor in an answer, you should be describing how to do so for that answer. Simply saying that one can use it feels like an incomplete – H Walters Sep 03 '17 at 01:48
2

How about:

#ifdef USE_ZIP
#  define IF_ZIP(arg) arg
#else
#  define IF_ZIP(arg)
#endif

#define X_MACRO(MAC) \
  MAC(foo)           \
  MAC(bar)           \
  IF_ZIP(MAC(zip))
HTNW
  • 27,182
  • 1
  • 32
  • 60
  • I like this approach (and I hadn't considered it) because it still results in a single canonical list (the `X_MACRO` above), and moves the conditional part out to basically boilerplate `IF_*` macros which can mostly be ignored (and are all the same). The other solutions involve constructing the list in multiple parts. – BeeOnRope Sep 09 '17 at 22:58
  • Ultimately I chose Travis' answer because it's factors even more out, although it may very well have been inspired by this one. – BeeOnRope Sep 09 '17 at 22:59