Athena
Athena

Reputation: 213

Is there a portable way to implement variadic CHECK and PROBE macros for detecting the number of macro arguments in C++?

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

Answers (2)

Athena
Athena

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

Yujian Yao - MSFT
Yujian Yao - MSFT

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;
}

enter image description here

Upvotes: 2

Related Questions