问题
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?
回答1:
Inspired by chris's answer.
#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
.
回答2:
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.
回答3:
As mentioned in the other answer, you can write your own OVERLOAD
macro. BOOST_PP_OVERLOAD
consists of two parts, BOOST_PP_CAT
and BOOST_PP_VARIADIC_SIZE
. However, unlike Boost, you only care about 2 args. So:
#define OVERLOAD(prefix, ...) CAT(prefix, VARIADIC(__VA_ARGS__))
CAT
will look like:
#define CAT(a, b) KITTY((a, b))
#define KITTY(par) MEOW ## par
#define MEOW(a, b) a ## b
And VARIADIC
:
#define VARIADIC(...) _VARIADIC_(__VA_ARGS__, 2, 1,)
#define _VARIADIC_(e0, e1, size, ...) size
回答4:
The problem with the solution as it is specified in its most popular answer above is that the compiler is free to issue a warning, or even an error, if __VA_OPT__ is used outside of its C++20 mode, since the word is a compiler reserved word since it starts and ends with double underscores. In fact I have discovered that gcc will issue a warning or an error depending on the compiler options being used, although it will normally not do so in most compiling cases. Because of this surrounding any solution with a current test for C++20, such as:
# if defined(__cplusplus) && __cplusplus > 201703L
// Solution
#endif
is a more conservative solution although it limits the test to C++20 or higher.
来源:https://stackoverflow.com/questions/48045470/portably-detect-va-opt-support