6

I'd like to know how to apply a unary function (or another macro) to variadic arguments of a macro, like

int f(int a);

#define apply(args...) <the magic>
apply(a, b, c)

which unrolls

f(a)
f(b)
f(c)

Note that the number of arguments is unknown.

Jeffion
  • 91
  • 1
  • 7

2 Answers2

6

The code below is working for what you've asked for with up to 1024 arguments and without using additional stuff like boost. It defines an EVAL(...) and also a MAP(m, first, ...) macro to do recursion and to use for each iteration the macro m with the next parameter first.

With the use of that, your apply(...) looks like: #define apply(...) EVAL(MAP(apply_, __VA_ARGS__)).

It is mostly copied from C Pre-Processor Magic. It is also great explained there. You can also download these helper macros like EVAL(...) at this git repository, there are also a lot of explanation in the actual code. It is variadic so it takes the number of arguments you want.

But I changed the FIRST and the SECOND macro as it uses a Gnu extension like it is in the source I've copied it from. This is said in the comments below by @HWalters:

Specifically, 6.10.3p4: "Otherwise [the identifier-list ends in a ...] there shall be more arguments in the invocation than there are parameters in the macro definition (excluding the ...)".

Main function part:

int main()
{
   int a, b, c;
   apply(a, b, c) /* Expands to: f(a); f(b); f(c); */

   return 0;
}

Macro definitions:

#define FIRST_(a, ...) a
#define SECOND_(a, b, ...) b

#define FIRST(...) FIRST_(__VA_ARGS__,)
#define SECOND(...) SECOND_(__VA_ARGS__,)

#define EMPTY()

#define EVAL(...) EVAL1024(__VA_ARGS__)
#define EVAL1024(...) EVAL512(EVAL512(__VA_ARGS__))
#define EVAL512(...) EVAL256(EVAL256(__VA_ARGS__))
#define EVAL256(...) EVAL128(EVAL128(__VA_ARGS__))
#define EVAL128(...) EVAL64(EVAL64(__VA_ARGS__))
#define EVAL64(...) EVAL32(EVAL32(__VA_ARGS__))
#define EVAL32(...) EVAL16(EVAL16(__VA_ARGS__))
#define EVAL16(...) EVAL8(EVAL8(__VA_ARGS__))
#define EVAL8(...) EVAL4(EVAL4(__VA_ARGS__))
#define EVAL4(...) EVAL2(EVAL2(__VA_ARGS__))
#define EVAL2(...) EVAL1(EVAL1(__VA_ARGS__))
#define EVAL1(...) __VA_ARGS__

#define DEFER1(m) m EMPTY()
#define DEFER2(m) m EMPTY EMPTY()()

#define IS_PROBE(...) SECOND(__VA_ARGS__, 0)
#define PROBE() ~, 1

#define CAT(a,b) a ## b

#define NOT(x) IS_PROBE(CAT(_NOT_, x))
#define _NOT_0 PROBE()

#define BOOL(x) NOT(NOT(x))

#define IF_ELSE(condition) _IF_ELSE(BOOL(condition))
#define _IF_ELSE(condition) CAT(_IF_, condition)

#define _IF_1(...) __VA_ARGS__ _IF_1_ELSE
#define _IF_0(...)             _IF_0_ELSE

#define _IF_1_ELSE(...)
#define _IF_0_ELSE(...) __VA_ARGS__

#define HAS_ARGS(...) BOOL(FIRST(_END_OF_ARGUMENTS_ __VA_ARGS__)())
#define _END_OF_ARGUMENTS_() 0

#define MAP(m, first, ...)           \
  m(first)                           \
  IF_ELSE(HAS_ARGS(__VA_ARGS__))(    \
    DEFER2(_MAP)()(m, __VA_ARGS__)   \
  )(                                 \
    /* Do nothing, just terminate */ \
  )
#define _MAP() MAP

#define apply_(x) f(x);
#define apply(...) EVAL(MAP(apply_, __VA_ARGS__))

To test macro expansion it is useful to use gcc with the command line argument -E:

$ gcc -E srcFile.c

because your're getting concrete error messages and understand what's going on.

Andre Kampling
  • 5,476
  • 2
  • 20
  • 47
  • 1
    `FIRST` as shown here relies on non-standard gnu extensions. This implementation, per the standard, requires two arguments; your use of it requires `FIRST` to accept one argument. `#define FIRST(...) FIRST_(__VA_ARGS__,)` `#define FIRST_(x, ...) x` is a fix. Similar with `SECOND`. – H Walters Aug 10 '17 at 13:33
  • Also, `HAS_ARGS` is at best poorly named; it's not really testing if it is passed no arguments. Rather, it's testing whether its first argument is empty. – H Walters Aug 10 '17 at 13:40
  • @HWalters: Thank you! As I said it's mostly copied from the source I've mentioned in the answer. I didn't get why `FIRST` should not work for all compiler/preprocessors and why your version is a fix for that? "My version" just throws aways the `__VA_ARGS__` why this is an gnu extension? – Andre Kampling Aug 10 '17 at 13:43
  • 1
    Specifically, 6.10.3p4: "Otherwise [the identifier-list ends in a ...] there shall be more arguments in the invocation than there are parameters in the macro definition (excluding the ...)". (I know you asked for a link, but the text fits in a comment). `FIRST(a, ...)` thus requires at least two arguments. Accepting 1 is a gnu extension tied to its comma elision feature. – H Walters Aug 10 '17 at 13:47
  • @HWalters: Thank you very much got it and changed it! @ZhaoxiongCui: There was also another little error as it expanded to: `f(a); , f(b); , f(c);` with commas in between before. Now the expansion works and it expands to: `f(a); f(b); f(c);`. – Andre Kampling Aug 10 '17 at 13:56
  • Hi @Xhaoxiong if this or any answer has solved your question please consider [accepting it](https://meta.stackexchange.com/q/5234/179419) by clicking the check-mark. This indicates to the wider community that you've found a solution and gives some reputation to both the answerer and yourself. There is no obligation to do this. – Andre Kampling Aug 16 '17 at 06:35
2

Everything is possible in C if you throw enough ugly macros at it. For example, you can have an ugly function-like macro:

#include <stdio.h>

int f (int a)
{
  printf("%d\n", a);
}

#define SIZEOF(arr) (sizeof(arr) / sizeof(*arr))

#define apply(...)                    \
{                                     \
  int arr[] = {__VA_ARGS__};          \
  for(size_t i=0; i<SIZEOF(arr); i++) \
  {                                   \
    f(arr[i]);                        \
  }                                   \
}

int main (void)
{
  apply(1, 2, 3);
}

Notice that 1) This would be much better off as a variadic function, and 2) it would be even better if you get rid of the variadic nonsense entirely and simply make a function such as

int f (size_t n, int array[n])
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • may use `do{...}while(0)` when defining apply. – ch271828n Oct 09 '19 at 06:12
  • @fzyzcjy Depends on the context. If the aim is to produce high quality C code, then no - high quality C code doesn't use `if() x(); else` without braces `{}` and allowing such code is the only purpose of do-while(0). If the aim is create some portable lib that can be used by all manner of users with diverse code quality standards, then do-while(0) should be used. – Lundin Oct 09 '19 at 06:25
  • Thank you! I have seldom heard of this kind of explanation – ch271828n Oct 09 '19 at 07:21
  • 1
    @fzyzcjy This is the reason why MISRA-C dropped the requirement of using do-while(0). It was required for function-like macros in older versions of MISRA, but they dropped the rule in the latest version, because MISRA-C already got the requirement that we must always use braces after all control and loop statements. – Lundin Oct 09 '19 at 08:22