MSVC treatment of ##__VA_ARGS__ vs. other compilers

Viewed 757

As an extension to the standard, the ## preprocessor operator can be used to "eat" a trailing comma for __VA_ARGS__. This is supported in all my compilers of interest (GCC, clang, MSVC).

However, their handling of it is not the same. In particular, MSVC breaks in a way I don't understand (example below). How can I write the MYLOG macro so that it works in all platforms?

Example program (live link)

#include<cstdio>

#define LOG_HELPER(...)    printf(__VA_ARGS__)
#define MYLOG(fmt, ...)    LOG_HELPER("mylog: " fmt, ##__VA_ARGS__)

#define MYPRINTF(fmt, ...) printf(   "printf: " fmt, ##__VA_ARGS__)

int main() {

    // Works in all cases I care about (GCC, clang, MSVC)
    MYPRINTF("foo\n");

    // Works in GCC+clang, but not MSVC!
    MYLOG("foo\n");

    return 0;
}

The error MSVC gives is: error C2059: syntax error: ')'

Indeed, looking at the processed output, the MYLOG line expands to:

printf("foo\n", )  // <- Note trailing comma - bad!

Is there a semi-clean way to get this to work in all cases?

Note: My main desire here is to get this to work in a cross-platform way. I realize ## is non-standard, but I'm more interested in "works on MSVC+GCC+clang" than I am with strict standards compliance.

Also: I am aware of __VA_OPT__ coming in C++20, but it is not an option for me right now.

4 Answers

The easy way would be to have different definitions based on detected compiler. And note that MSVC (at least prior to the /Zc:preprocessor switch) will eat a trailing comma if VA_ARGS is empty.

#define LOG_HELPER(...)    printf(__VA_ARGS__)
#if defined(_MSC_VER)
#define MYLOG(fmt, ...)    LOG_HELPER("mylog: " fmt, __VA_ARGS__)
#else
#define MYLOG(fmt, ...)    LOG_HELPER("mylog: " fmt, ##__VA_ARGS__)
#endif

I found an approach that works okay for my purposes:

#define MYLOG(...)    LOG_HELPER("mylog: " __VA_ARGS__)

It is, sadly, a bit specific to this particular usage (combining string literals).

A little late to the party, but simply changing your LOG_HELPER() macro to the following seems to make it work on all platforms you care about:

#define LOG_HELPER(fmt, ...) printf(fmt, ##__VA_ARGS__)

Working example

The ... is not allowed to map against 0 parameters.
Note: This may have been fixed in the standard now (not sure) but not all compilers have caught up.

MYLOG("foo\n");  // Here the expansion of ... maps to zero parameters.

The easy way to get around this is to add another layer and an artificial parameter you ignore to the end.

#define LOG_HELPER_INDIRECT(fmt, ...) LOG_HELPER("mylog: " fmt, ##__VA_ARGS__)
#define MYLOG(...)                    LOG_HELPER_INDIRECT(__VA_ARGS__, 1)

This way the ... always maps to at least one parameter.
You just have to make sure that one parameter is ignored which in this case it will be,

Related