Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problems with variadic macros in C

I'm having a problem with optional arguments in #define statements in C, or more specifically with gcc 4.2:

bool func1(bool tmp) { return false; }
void func2(bool tmp, bool tmp2) {}
#define CALL(func, tmp, ...) func(tmp, ##__VA_ARGS__)

int main() {
   // this compiles
   CALL(func2, CALL(func1, false), false);

   // this fails with: Implicit declaration of function 'CALL'
   CALL(func2, false, CALL(func1, false));
}

That's obviously a contrived example, but does show the problem. Does anyone know how I can get the optional arguments to "resolve" correctly?


Additional information: If I remove the ## before __VA_ARGS__, and do something like this:

bool func2(bool tmp, bool tmp2) { return false; }
#define CALL(func, tmp, ...) func(tmp, __VA_ARGS__)

int main() {
   CALL(func2, false, CALL(func2, false, false));
}

That compiles, but it no longer works with zero arguments since it would resolve to func(tmp, )

EDIT: Right after converting all of my code to rely on P99 instead of what I had earlier (which ended up breaking my code considerably, whoops), I accidentally discovered that this works:

bool func1(bool tmp) { return false; }
void func2(bool tmp, bool tmp2) {}
#define CALL2(func, tmp, p...) func(tmp, ##p)
#define CALL(func, tmp...) CALL2(func, tmp)

int main() {
   // works
   CALL(func2, CALL(func1, false), false);

   // ...also works
   CALL(func2, false, CALL(func1, false));
}

Compiles and works with any number of parameters (and the correct values are passed in and returned), but... is this supposed to be legal?

like image 567
BonzaiThePenguin Avatar asked Dec 03 '22 03:12

BonzaiThePenguin


2 Answers

The ## operator does exact token substitution, so in that case it's trying to send the token "CALL(func1, false)" as the last argument to the func1 C function.

The problem with this is that CALL is a macro, and you cannot nest variadic macro invocations within a ##__VA_ARGS__ list.

The reason why it works when the inner macro is passed as a named argument is because the preprocessor will parse named arguments for inner macros, but not ##__VA_ARGS__ lists, where there is just simple token substitution.

One way to solve this is to assign the result of the inner CALL to a placeholder variable and then pass that along to the macro.

int main() {
   CALL(func2, CALL(func1, false), false);

   bool result = CALL(func1, false);
   CALL(func2, false, result);
}

Another way to solve this is to just use __VA_ARGS__ as the only argument to the func function, and that would allow you to pass in nested macro invocations, like so:

#define CALL(func, ...) func(__VA_ARGS__)

int main() {
   CALL(func2, false, CALL(func2, false, false));
}

Let's analyze your dilemma in a bit more detail:

CALL(func2, false, CALL(func1, false))

In this particular macro invocation, CALL is now ("func2", "tmp", CALL(func1, false)) So it tries to invoke func1, passing in tmp, and, well, CALL(func1, false).

This is where the line is drawn between the preprocessor and the actual C compiler.

The preprocessor, once it starts doing substitution, it's done parsing, so the compiler receives CALL(func1, false) as an actual C function, not a macro, because the compiler doesn't know about macros, only the preprocessor does.

like image 110
Jacob Relkin Avatar answered Dec 04 '22 17:12

Jacob Relkin


You are using a gcc extension with the , ## construct. It is probably not a good idea to use that if you have portability in mind.

With a little bit of effort you can construct macros that may react on the number of arguments they receive and do the right replacement. P99 provides helpers for that:

#define CALL(...) P99_IF_EQ_2(P99_NARG(__VA_ARGS__))(dosomethingwithtwo(__VA_ARGS__))(dosomethingwithmore(__VA_ARGS__))

But in your case I think there is an easy solution:

#define CALL(func, ...) func(__VA_ARGS__)
like image 22
Jens Gustedt Avatar answered Dec 04 '22 18:12

Jens Gustedt