Reputation: 213
In C Preprocessor tricks, tips, and idioms, it suggests the following macros which detect the number of arguments created by a macro:
#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)
#define PROBE(x) x, 1,
and then states that:
CHECK(PROBE(~)) // Expands to 1
CHECK(xxx) // Expands to 0
However in MSVC 2019, compiling for C++17, the above two CHECK()
's both expand to 0. Godbolt shows me that GCC and Clang expand the macros as expected, but MSVC does not. It seems to be pasting __VA_ARGS__
from CHECK(...)
as a single token even if it contains commas...?
I have heard that MSVC is nonconforming with regard to macros in some ways, but I am not clear on the details.
Is there a way to make these macros work for MSVC, and ideally still work for GCC/Clang? (I'd rather not #ifdef
a separate implementation if possible.)
Upvotes: 1
Views: 297
Reputation: 213
After some more digging I found this answer from the VS Developer Community, which provides the solution: an extra layer of indirection and some funky rebracketing. Rewriting to match the original question:
#define CHECK_N(x, n, ...) n
#define CHECK_IMPL(tuple) CHECK_N tuple //note no brackets here
#define CHECK(...) CHECK_IMPL((__VA_ARGS__, 0)) //note the double brackets here
#define PROBE(x) x, 1
godbolt demonstrates that this works across MSVC, gcc, and clang. Some of the other macro tools from the original link also require some adjustments (eg IIF(x)
) but again, more layers of indirection seem to solve those too.
I hope one day to be able to use the compiler option /Zc:preprocessor as was mentioned elsewhere, which also fixes these macros, unfortunately that breaks certain other libraries (such as the Windows SDK).
Upvotes: 1
Reputation: 954
I suggest you read this issue carefully, which mentions
The traditional Microsoft C++ implementation suppresses a trailing comma if no arguments are passed to the ellipsis. When the /Zc:preprocessor compiler option is set, the trailing comma isn't suppressed.
Opening /Zc:preprocessor requires the following steps:
1.Open the project's Property Pages dialog box.
2.Select the Configuration Properties > C/C++ > Preprocessor property page.
3.Modify the Use Standard Conforming Preprocessor property and then choose Yes.
I tested with the following code and got the expected result:
#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)
#define PROBE(x) x, 1
#define IS_PAREN(x) CHECK(IS_PAREN_PROBE x)
#define IS_PAREN_PROBE(...) PROBE(~)
#include<iostream>
using namespace std;
int main()
{
cout << CHECK(PROBE(~)) << endl;
cout << CHECK(xxx) << endl;
cout << IS_PAREN(()) << endl;
cout << IS_PAREN(xxx) << endl;
return 0;
}
Upvotes: 2