4

Need a way to support nested calls in variadic macros with optional argument. same as this but C99 compatible

GNU gcc extension for ## operator prevents nested calls from being expanded, see code below.

#define send(obj, msg, ...) find_method(obj, msg)(obj, ##__VA_ARGS__)

/* Ok */
send(0, "+", 1);
find_method(0, "+")(0, 1);

/* Ok. nested call as macros named argument */
send(send(5, "increment"), "+", 1);
find_method(find_method(5, "increment")(5), "+")(find_method(5, "increment")(5), 1);

/* Fail. nested call as macros variable argument i.e. `send` macro is not expanded */
send(0, "+", send(5, "increment"));
find_method(0, "+")(0, send(5, "increment"));

/*
 * preprocess with next commands
 *
 * gcc-4.9   -Wall -std=c99 -E -c file.c | less
 * clang-3.8 -Wall -std=c99 -E -c file.c | less
 */

I've modified Jens Gustedt solution to place optional comma, see code below.

Are there more concise alternatives ?

#define _ARG16(              \
    _0,  _1,  _2,  _3,  _4,  \
    _5,  _6,  _7,  _8,  _9,  \
    _10, _11, _12, _13, _14, \
    _15, ...) _15
#define HAS_COMMA(...) \
    _ARG16(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
#define _TRIGGER_PARENTHESIS_(...) ,

#define OPTIONAL_COMMA(...)                                             \
    _ISEMPTY(                                                           \
          /* test if there is just one argument, eventually an empty    \
             one */                                                     \
          HAS_COMMA(__VA_ARGS__),                                       \
          /* test if _TRIGGER_PARENTHESIS_ together with the argument   \
             adds a comma */                                            \
          HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__),                 \
          /* test if the argument together with a parenthesis           \
             adds a comma */                                            \
          HAS_COMMA(__VA_ARGS__ (/*empty*/)),                           \
          /* test if placing it between _TRIGGER_PARENTHESIS_ and the   \
             parenthesis adds a comma */                                \
          HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__ (/*empty*/))      \
    )

#define SPACE_SYMBOL
#define COMMA_SYMBOL ,
#define ARG3(_0, _1, _2, ...) _2
#define OPTIONAL_COMMA_FROM(...) ARG3(__VA_ARGS__, SPACE_SYMBOL, COMMA_SYMBOL)

#define PASTE5(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4
#define _ISEMPTY(_0, _1, _2, _3) \
    OPTIONAL_COMMA_FROM(PASTE5(_IS_EMPTY_CASE_, _0, _1, _2, _3))
#define _IS_EMPTY_CASE_0001 ,

#define send(obj, msg, ...) \
    find_method(obj, msg)(obj OPTIONAL_COMMA(__VA_ARGS__) __VA_ARGS__)

/* Ok */
send(0, "+", 1);

/* Ok */
send(send(5, "increment"), "+", 1);

/* Ok */
send(0, "+", send(5, "increment"));

Update:

H Walters thanks for idea.

Didn't noticed approach with macros overloading in first place

#define SEND_NO_ARG(obj, msg) find_method(obj, msg)(obj)
#define SEND_ARG(obj, msg, ...) find_method(obj, msg)(obj, __VA_ARGS__)

#define GET_18TH_ARG(arg1, arg2, arg3, arg4, arg5,        \
    arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13,   \
    arg14, arg15, arg16, arg17, arg18, ...) arg18
#define SEND_MACRO_SELECTOR(...)                          \
    GET_18TH_ARG(__VA_ARGS__,                             \
        SEND_ARG, SEND_ARG, SEND_ARG, SEND_ARG, SEND_ARG, \
        SEND_ARG, SEND_ARG, SEND_ARG, SEND_ARG, SEND_ARG, \
        SEND_ARG, SEND_ARG, SEND_ARG, SEND_ARG, SEND_ARG, \
        SEND_NO_ARG, )

#define send(...) SEND_MACRO_SELECTOR(__VA_ARGS__)(__VA_ARGS__)
0

3 Answers 3

2

This approach is slightly more concise (10 macros vs 12), and at least to my eyes a bit more organized (more of the macros are general purpose):

#define GLUE(A,B) GLUE_I(A,B)
#define GLUE_I(A,B) A##B
#define FIRSTOFMANY(X,...) X
// This can be used as a pattern matcher on the first argument.
// Generally, the first argument is ignored; but if it's
// an object-like macro whose expansion has a comma, it can
// shift a new second argument in that's returned.
// Thus, you can build a "query pattern" as the first argument,
// pass a "default" as the second, and override the default with
// "matched pattern macros".
#define SECOND(...) SECOND_I(__VA_ARGS__,,)
#define SECOND_I(_,X,...) X
// Expands to a count of arguments (min 1, max 15).
#define COUNT(...) COUNT_I(__VA_ARGS__,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,)
#define COUNT_I(_,_15,_14,_13,_12,_11,_10,_9,_8,_7,_6,_5,_4,_3,_2,X,...) X

#define send(obj, ...) \
    find_method(obj, FIRSTOFMANY(__VA_ARGS__,)) \
    ( \
      /* select a macro based on ... count (default SEND_LONG) */ \
      SECOND(GLUE(WHEN_SEND_VCNT_EQ_,COUNT(__VA_ARGS__)),SEND_LONG)\
      (obj, __VA_ARGS__) \
    )
#define SEND_LONG(X,Y,...) X,__VA_ARGS__
// Use FIRSTOFMANY when send's varying arg count is 1
#define WHEN_SEND_VCNT_EQ_1 , FIRSTOFMANY

...it's also C99 compliant (not relying on gnu extensions).

1

macros overloading technique provides shortest code so far.

#define SEND_NO_ARG(obj, msg) find_method(obj, msg)(obj)
#define SEND_ARG(obj, msg, ...) find_method(obj, msg)(obj, __VA_ARGS__)

#define GET_18TH_ARG(arg1, arg2, arg3, arg4, arg5,        \
    arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13,   \
    arg14, arg15, arg16, arg17, arg18, ...) arg18
#define SEND_MACRO_SELECTOR(...)                          \
    GET_18TH_ARG(__VA_ARGS__,                             \
        SEND_ARG, SEND_ARG, SEND_ARG, SEND_ARG, SEND_ARG, \
        SEND_ARG, SEND_ARG, SEND_ARG, SEND_ARG, SEND_ARG, \
        SEND_ARG, SEND_ARG, SEND_ARG, SEND_ARG, SEND_ARG, \
        SEND_NO_ARG, )

#define send(...) SEND_MACRO_SELECTOR(__VA_ARGS__)(__VA_ARGS__)
0
GNU gcc extension for ## operator prevents nested calls from being expanded, see code below.

If you're willing to use the gnu extension, here's an even more concise approach:

#define COMMAVA(...) ,##__VA_ARGS__
#define send(obj, msg, ...) find_method(obj, msg)(obj COMMAVA(__VA_ARGS__))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.