便携式检测 __VA_OPT__ 支持?

Portably detect __VA_OPT__ support?

在 C++20 中,预处理器支持 __VA_OPT__ 作为一种在参数数量大于零的情况下可选地扩展可变参数宏中标记的方法。 (这避免了对 ##__VA_ARGS__ GCC 扩展的需要,这是一个不可移植且丑陋的 hack。)

Clang SVN 已经实现了这个功能,但是他们还没有为它添加功能测试宏。任何聪明的预处理器黑客都可以想出一种方法来检测是否存在 __VA_OPT__ 支持,而不会导致硬错误或可移植性警告吗?

像下面这样的东西应该可以工作,尽管你可以改进它:

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

在 Clang 主干上,这在 C++2a 模式下计算为 1,在 C++17 模式下计算为 0。 GCC 主干实际上在 C++17 中将其评估为 1,但也在该模式下处理 __VA_OPT__

它所做的是使用 BOOST_PP_OVERLOAD 根据参数的计数调用 _1_2 版本的 _II。如果 __VA_OPT__(,) 扩展为 ,,将有 2 个空参数。如果不是,将有 1 个空参数。我们总是用参数列表调用这个宏,所以任何支持 __VA_OPT__ 的编译器都应该总是将它扩展为 ,.

当然,Boost.PP 依赖项不是强制性的。一个简单的 1-or-2-arg OVERLOAD 宏应该很容易替换。失去一点通用性以使其更直接:

#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__(,))

有一个来自 Clang 的可移植性警告:

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

我不知道如果没有 C++11 可变参数宏支持,这种检测是否可行。您可以考虑假设不支持低于 C++11 的 __cplusplus 值,但即使包裹在这样的检查中,Clang 仍然会发出警告。

灵感来自 .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(?)

如果支持__VA_OPT__VA_OPT_SUPPORTED_I(?)扩展为PP_THIRD_ARG(,,true,false,),所以第三个参数为true;否则,VA_OPT_SUPPORTED_I(?) 扩展为 PP_THIRD_ARG(__VA_OPT__(,),true,false,),第三个参数是 false.


编辑:如 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。可能需要特例 GCC 以避免此诊断。

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

1.正如克里斯提到的,如果 __VA_OPT__(,) 扩展为 ,,将会有 2 个空参数,否则会有 1 个参数。所以可以测试 PP_NARG(__VA_OPT__(,)) == 2,其中 PP_NARG is a macro to count the number of arguments。为了适应这个测试,PP_NARG的定义可以简化和内联

正如另一个答案中提到的,您可以编写自己的 OVERLOAD 宏。 BOOST_PP_OVERLOAD由两部分组成,BOOST_PP_CATBOOST_PP_VARIADIC_SIZE。然而,与 Boost 不同的是,您只关心 2 个参数。所以:

#define OVERLOAD(prefix, ...) CAT(prefix, VARIADIC(__VA_ARGS__))

CAT 看起来像:

#define CAT(a, b) KITTY((a, b))
#define KITTY(par) MEOW ## par
#define MEOW(a, b) a ## b

VARIADIC

#define VARIADIC(...) _VARIADIC_(__VA_ARGS__, 2, 1,)
#define _VARIADIC_(e0, e1, size, ...) size

解决方案的问题正如上面最受欢迎的答案中所指出的那样,如果 __VA_OPT__ 在其 C++ 之外使用,编译器可以自由发出警告,甚至是错误20 模式,因为这个词是编译器保留字,因为它以双下划线开头和结尾。事实上,我发现 gcc 会根据所使用的编译器选项发出警告或错误,尽管在大多数编译情况下它通常不会这样做。因此,围绕任何具有 C++20 当前测试的解决方案,例如:

# if defined(__cplusplus) && __cplusplus > 201703L
// Solution
#endif

是一个更保守的解决方案,尽管它将测试限制为 C++20 或更高版本。