I am developing a program using a third-party UI library with functions in the form Vbox(void *first, ...)
. These serve as layout functions and take an arbitrary number of parameters. The end of the list is defined by the first NULL detected. This means that I need to remember to end my list with a NULL, something I often fail to do.
So I created a few auxiliary macros which should expand to append my list with a NULL.
These are of the form:
#define UtlVbox(first, ...) Vbox(first, ##__VA_ARGS__, NULL)
The ##
before the __VA_ARGS__
serve to get rid of the previous comma in case __VA_ARGS
is empty.
I need the first
in case the box should actually be initialized empty (Vbox(NULL)
): in these cases, the user must explicitly add the NULL because I can't get rid of the ,
after the __VA_ARGS__
(since the ##
hack only works if the comma is before the ##
, not after), so an explicit NULL must be given by the user, which will result in the following expansion: Vbox(NULL, NULL)
, which is a bit redundant but fine.
This works well overall, but I've bumped into an odd situation I can't quite understand.
Take the following file, for example:
// expand.c
void* Vbox(void* first, ...);
void* Hbox(void* first, ...);
#define UtlVbox(first, ...) Vbox(first, ##__VA_ARGS__, NULL)
#define UtlHbox(first, ...) Hbox(first, ##__VA_ARGS__, NULL)
static void* Test()
{
return UtlHbox(
Foo,
UtlVbox(
UtlHbox(Bar)));
}
If I run gcc -E expand.c
, I get the following output:
# 1 "expand.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "expand.c"
void* Vbox(void* first, ...);
void* Hbox(void* first, ...);
static void* Test()
{
return Hbox(Foo, Vbox(UtlHbox(Bar), NULL), NULL);
}
Everything is expanded precisely as expected, except for the innermost UtlHbox, which for some reason hasn't been expanded and therefore throws an error on compilation. (Also, the NULL's weren't expanded in this example since there aren't any relevant #include's). In VC12 (Visual Studio 2013), things compile just fine.
What's happening here? Is this a conflict between the different meanings of the ##
operation? Is there any way to solve this?
I'm using GCC 4.6.3, but I've tried compiling this on GodBolt with GCC 7.1 and get the same results.
After some research, I'm starting to think I've bumped into a known problem in GCC.
It seems like GCC is tripping up on itself. If I create a third macro
#define UtlZbox(first, ...) Zbox(first , ##__VA_ARGS__, NULL)
and replace the inner UtlHbox in the example above with this new macro, the output is correct:
static void* Test()
{
return Hbox(Foo, Vbox(Zbox(Bar, NULL), NULL), NULL);
}
It seems like GCC trips over itself when a variadic macro is repeated within another instance of itself.
I've done a few other tests (modifying the macros to ease visualization):
#define UtlVbox(first, ...) V(first,##__VA_ARGS__)
#define UtlHbox(first, ...) H(first,##__VA_ARGS__)
int main()
{
// HHH
UtlHbox(UtlHbox(UtlHbox(1)));
UtlHbox(UtlHbox(UtlHbox(2, 1)));
UtlHbox(UtlHbox(2, UtlHbox(1)));
UtlHbox(2, UtlHbox(UtlHbox(1)));
UtlHbox(3, UtlHbox(2, UtlHbox(1)));
// HHV
UtlHbox(UtlHbox(UtlVbox(1)));
UtlHbox(UtlHbox(UtlVbox(2, 1)));
UtlHbox(UtlHbox(2, UtlVbox(1)));
UtlHbox(2, UtlHbox(UtlVbox(1)));
UtlHbox(3, UtlHbox(2, UtlVbox(1)));
// HVH
UtlHbox(UtlVbox(UtlHbox(1)));
UtlHbox(UtlVbox(UtlHbox(2, 1)));
UtlHbox(UtlVbox(2, UtlHbox(1)));
UtlHbox(2, UtlVbox(UtlHbox(1)));
UtlHbox(3, UtlVbox(2, UtlHbox(1)));
// VHH
UtlVbox(UtlHbox(UtlHbox(1)));
UtlVbox(UtlHbox(UtlHbox(2, 1)));
UtlVbox(UtlHbox(2, UtlHbox(1)));
UtlVbox(2, UtlHbox(UtlHbox(1)));
UtlVbox(3, UtlHbox(2, UtlHbox(1)));
return 0;
}
Here's Godbolt's output, compiling with GCC 7.1 (doing it on my machine with 4.6.3 gives identical output):
Successful conversions are marked with green arrows, failures are red. The problem seems to be when a macro X with variadic arguments is placed anywhere in the variadic arguments of another instance of X (even if as an argument (variadic or not) of some other macro Y).
The last block of tests (marked as // Failures...
) is a repeat of all the previous cases which failed, only replacing whichever macro failed to expand with a UtlZbox. Doing this caused proper expansion in every single case except the one where a UtlZbox is placed in the variadic argument of another UtlZbox.