Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Portably detect __VA_OPT__ support?

In C++20, the preprocessor supports __VA_OPT__ as a way to optionally expand tokens in a variadic macro if the number of arguments is greater than zero. (This obviates the need for the ##__VA_ARGS__ GCC extension, which is a non-portable and ugly hack.)

Clang SVN has implemented this feature, but they haven't added a feature test macro for it. Can any clever preprocessor hacker figure out a way to detect the presence or absence of __VA_OPT__ support without causing a hard error or a portability warning?

like image 594
Eric Niebler Avatar asked Dec 31 '17 20:12

Eric Niebler


People also ask

What is __ Va_opt __?

The key to making this work is a new pre-processor feature in C++20, __VA_OPT__(x) , which expands to x when a variable-argument macro has more than zero arguments and to nothing otherwise.

What is __ Va_args __ in C?

__VA_ARGS__ is replaced by all of the arguments that match the ellipsis, including commas between them. The C Standard specifies that at least one argument must be passed to the ellipsis to ensure the macro doesn't resolve to an expression with a trailing comma.


2 Answers

Inspired by chris's answer.1

#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(?) 

If __VA_OPT__ is supported, VA_OPT_SUPPORTED_I(?) expands to PP_THIRD_ARG(,,true,false,), so the third argument is true; otherwise, VA_OPT_SUPPORTED_I(?) expands to PP_THIRD_ARG(__VA_OPT__(,),true,false,), the third argument is false.


Edit: As Edward Diener's answer notes, GCC >= 8 issues a warning or error whenever it sees __VA_OPT__, if -pedantic mode is on and __VA_OPT__ is not enabled (e.g. in -std=c++17). This is GCC bug 98859. One might have to special-case GCC to avoid this diagnostic.

#if __cplusplus <= 201703 && defined __GNUC__ \   && !defined __clang__ && !defined __EDG__ // These compilers pretend to be GCC #  define VA_OPT_SUPPORTED false #endif 

1. As chris mentions, if __VA_OPT__(,) expands to ,, there will be 2 empty arguments, otherwise there will be 1 argument. So it's possible to test PP_NARG(__VA_OPT__(,)) == 2, where PP_NARG is a macro to count the number of arguments. To adapt to this test, the definition of PP_NARG can be simplified and inlined.

like image 61
cpplearner Avatar answered Sep 23 '22 05:09

cpplearner


Something like the following should work, though you might be able to improve it:

#include <boost/preprocessor.hpp>  #define VA_OPT_SUPPORTED_II_1(_) 0 #define VA_OPT_SUPPORTED_II_2(_1, _2) 1  #define VA_OPT_SUPPORTED_I(...) BOOST_PP_OVERLOAD(VA_OPT_SUPPORTED_II_, __VA_OPT__(,))(__VA_OPT__(,))  #define VA_OPT_SUPPORTED VA_OPT_SUPPORTED_I(?) 

On Clang trunk, this evaluates to 1 in C++2a mode and 0 in C++17 mode. GCC trunk actually evaluates this to 1 in C++17, but also handles __VA_OPT__ in that mode.

What this does is use BOOST_PP_OVERLOAD to call either the _1 or _2 version of _II based on the count of arguments. If __VA_OPT__(,) expands to ,, there will be 2 empty arguments. If not, there will be 1 empty argument. We always call this macro with an argument list, so any compiler supporting __VA_OPT__ should always expand it to ,.

Naturally, the Boost.PP dependency isn't mandatory. A simple 1-or-2-arg OVERLOAD macro should be easy enough to replace. Losing a bit of generality to make it more straightforward:

#define OVERLOAD2_I(_1, _2, NAME, ...) NAME #define OVERLOAD2(NAME1, NAME2, ...) OVERLOAD2_I(__VA_ARGS__, NAME2, NAME1)  #define VA_OPT_SUPPORTED_I(...) OVERLOAD2(VA_OPT_SUPPORTED_II_1, VA_OPT_SUPPORTED_II_2, __VA_OPT__(,))(__VA_OPT__(,)) 

There is one portability warning from Clang:

warning: variadic macros are incompatible with C++98 [-Wc++98-compat-pedantic]

I don't know if this detection is even possible without C++11 variadic macro support. You could consider assuming no support for __cplusplus values lower than C++11, but Clang still gives the warning even when wrapped in such a check.

like image 43
chris Avatar answered Sep 20 '22 05:09

chris