Search code examples
c++variadic-macrosboost-preprocessor

__VA_ARGS__ sees extra empty arg when invoked inside BOOST_PP_SEQ_FOR_EACH


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?


Solution

  • 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)