0

My actual code example is quite complex, but I will try to summarize the behavior I am seeing with a simple illustration.

I have a macro that I want to be able to call individually, or multiple times as a part of a larger macro expansion:

#define DO_STUFF(name,...)   \
    STUFF1(name,__VA_ARGS__) \
    STUFF2(name,__VA_ARGS__) \
    STUFF3(name,__VA_ARGS__)

I can use DO_STUFF(dave, int, char) and similar variations directly in a source file and it generates the code I expect.

I also want to invoke big lists of the DO_STUFF macro with another list of inputs. In order to handle this case, I am using boost preprocessor with a sequence of tuples (or variadic sequence):

DO_LOTS_OF_STUFF(
      (dave, ball, pen)          \
      (alice, cat, dog, bicycle) \
      (peter, bird) )

My definition of DO_LOTS_OF_STUFF looks like:

#define DO_LOTS_OF_STUFF(SEQ)       \
   BOOST_PP_SEQ_FOR_EACH(              \
     INVOKE_DS, _,                     \
     BOOST_PP_VARIADIC_SEQ_TO_SEQ(SEQ) \
   )

#define INVOKE_DS( r, data, elem )      \
   DO_STUFF(BOOST_PP_TUPLE_ENUM(elem)); \

When I invoke DO_LOTS_OF_STUFF as illustrated above, STUFF1, STUFF2, and STUFF3 are all invoked with an extra comma at the end with an empty parameter.

If I break the expansion at the point that DO_STUFF is invoked (by changing its name), the preprocessor output looks like I expect:

DO_STUFF(dave, ball, pen)
DO_STUFF(alice, cat, dog, bicycle)
DO_STUFF(peter, bird)

If I break the expansion at the STUFF1, STUFF2, and STUFF3 level, they appear in the output with an extra empty parameter:

STUFF1(dave, ball, pen,)
STUFF2(dave, ball, pen,)
STUFF3(dave, ball, pen,)
STUFF1(alice, cat, dog, bicycle,)
STUFF2(alice, cat, dog, bicycle,)
STUFF3(alice, cat, dog, bicycle,)
STUFF1(peter, bird,)
STUFF2(peter, bird,)
STUFF3(peter, bird,)

Is this just one of those things to avoid in preprocessor meta-programming like "don't use ##"? "Don't use __VA_ARGS__ in nested macros"?

Any advice on how to define DO_STUFF or DO_LOTS_OF_STUFF to avoid this issue?

Colin Andrews
  • 191
  • 11
  • 1
    I find it really hard to believe you need to do this stuff. Or at least that you need to do it via the C preprocessor. –  Apr 05 '18 at 00:07
  • I assure you, I have a real world use case that makes sense. Most of what I am doing is accomplished with variadic templated types. The preprocessor is just being used to eliminate a lot of repetition in the final classes that use the templated types. The phony names above are just placeholders, but maybe they make it seem like my use case is trivial? – Colin Andrews Apr 05 '18 at 00:13

1 Answers1

0

I figured out what is going on.

Where I invoke DO_STUFF with SEQ_FOR_EACH I use BOOST_PP_TUPLE_ENUM:

#define INVOKE_DS( r, data, elem )      \
   DO_STUFF(BOOST_PP_TUPLE_ENUM(elem)); \

BOOST_PP_TUPLE_ENUM turns a tuple (a,b,c,d) into comma separated tokens without the parenthesis a,b,c,d.

I assumed that that meant that DO_STUFF(BOOST_PP_TUPLE_ENUM((a,b,c,d))) would see 4 arguments, but in fact it still sees only one that is a,b,c,d.

This expansion:

#define DO_STUFF(name,...)   \
    STUFF1(name,__VA_ARGS__) \
    STUFF2(name,__VA_ARGS__) \
    STUFF3(name,__VA_ARGS__)

Is actually adding the extra empty parameter in this case because __VA_ARGS__ is empty.

So the solution was pretty simple. I just created a different variant of the DO_STUFF macro that is used in the DO_LOTS_OF_STUFF case

#define DO_LOTS_OF_STUFF(SEQ)       \
   BOOST_PP_SEQ_FOR_EACH(              \
     INVOKE_DS, _,                     \
     BOOST_PP_VARIADIC_SEQ_TO_SEQ(SEQ) \
   )

#define INVOKE_DS( r, data, elem )      \
   DO_STUFF_1(BOOST_PP_TUPLE_ENUM(elem)); \

#define DO_STUFF_1(tuple_args)   \
    STUFF1(tuple_args) \
    STUFF2(tuple_args) \
    STUFF3(tuple_args)
Colin Andrews
  • 191
  • 11