Function-like macros with C++20 __VA_OPT__ error in manual code

Viewed 648

I am building the code from the great manual of Recursive Macros and C++20 __VA_OPT__: https://www.scs.stanford.edu/~dm/blog/va-opt.html

The code is

#include <iostream>

#define PARENS ()

// Rescan macro tokens 256 times
#define EXPAND(arg) EXPAND1(EXPAND1(EXPAND1(EXPAND1(arg))))
#define EXPAND1(arg) EXPAND2(EXPAND2(EXPAND2(EXPAND2(arg))))
#define EXPAND2(arg) EXPAND3(EXPAND3(EXPAND3(EXPAND3(arg))))
#define EXPAND3(arg) EXPAND4(EXPAND4(EXPAND4(EXPAND4(arg))))
#define EXPAND4(arg) arg

#define FOR_EACH(macro, ...)                                    \
  __VA_OPT__(EXPAND(FOR_EACH_HELPER(macro, __VA_ARGS__)))
#define FOR_EACH_HELPER(macro, a1, ...)                         \
  macro(a1)                                                     \
  __VA_OPT__(FOR_EACH_AGAIN PARENS (macro, __VA_ARGS__))
#define FOR_EACH_AGAIN() FOR_EACH_HELPER

#define ENUM_CASE(name) case name: return #name;

#define MAKE_ENUM(type, ...)                    \
enum type {                                     \
  __VA_ARGS__                                   \
};                                              \
constexpr const char *                          \
to_cstring(type _e)                             \
{                                               \
  using enum type;                              \
  switch (_e) {                                 \
  FOR_EACH(ENUM_CASE, __VA_ARGS__)              \
  default:                                      \
    return "unknown";                           \
  }                                             \
}

MAKE_ENUM(MyType, ZERO, ONE, TWO, THREE);

void
test(MyType e)
{
  std::cout << to_cstring(e) << " = " << e << std::endl;
}

int
main()
{
  test(ZERO);
  test(ONE);
  test(TWO);
  test(THREE);
}

According to https://en.cppreference.com/w/cpp/compiler_support#C.2B.2B20_features , __VA_OPT__ is supported in MSVC and Clang, and only partially in GCC.

But to my surprise the code from the manual works only in GCC, producing errors in other compilers:


<source>(36): warning C4003: not enough arguments for function-like macro invocation 'FOR_EACH_HELPER'
<source>(36): warning C4003: not enough arguments for function-like macro invocation 'ENUM_CASE'
<source>(36): error C2059: syntax error: 'case'
<source>(36): error C2065: 'ENUM_CASE': undeclared identifier
<source>(36): error C3861: 'FOR_EACH_HELPER': identifier not found
<source>(36): error C2059: syntax error: ')'
<source>(36): error C2146: syntax error: missing ';' before identifier 'default'

https://gcc.godbolt.org/z/PreYcbz7f

Does it mean that the manual code is not well-formed according to C++20 and requires some GCC-specific language extensions?

1 Answers

If you look here, I added a __VA_OPT__ support detector.

#define PP_THIRD_ARG(a,b,c,...) c
#define VA_OPT_SUPPORTED_I(...) PP_THIRD_ARG(__VA_OPT__(,),true,false,)
#define VA_OPT_SUPPORTED VA_OPT_SUPPORTED_I(?)

static_assert(VA_OPT_SUPPORTED);

gcc and clang pass it; the MSVC version does not.

I then looked at clang's warnings; it is a warning that tells you if you pass 0 arguments to ....

It appears to be spurious. I disabled it with -Wno-gnu-zero-variadic-macro-arguments and your code works in clang.

So whatever version of MSVC is on godbolt does not appear to support __VA_OPT__, while the gcc and clang versions do. And that clang version has a spurious warning in the warning set you selected.

To fix MSVC, pass /Zc:preprocessor. By default it uses a legacy preprocessor.

Related