Портативно обнаружить поддержку __VA_OPT__?
В С++ 20 препроцессор поддерживает __VA_OPT__
как способ опционального расширения токенов в переменном макросе, если количество аргументов больше нуля. (Это устраняет необходимость расширения ##__VA_ARGS__
GCC, которое является не переносным и уродливым взломом.)
Clang SVN реализовала эту функцию, но они не добавили для нее макрос проверки функций. Может ли любой умный хакер препроцессора определить способ обнаружения присутствия или отсутствия поддержки __VA_OPT__
, не вызывая серьезной ошибки или предупреждения о переносимости?
Ответы
Ответ 1
Вдохновленный chris 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(?)
Если поддерживается __VA_OPT__
, VA_OPT_SUPPORTED_I(?)
расширяется до PP_THIRD_ARG(,,true,false,)
, поэтому третий аргумент true
; в противном случае VA_OPT_SUPPORTED_I(?)
расширяется до PP_THIRD_ARG(__VA_OPT__(,),true,false,)
, третий аргумент false
.
Ответ 2
Что-то вроде следующего должно работать, хотя вы можете его улучшить:
#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 trunk это оценивается в 1 в режиме С++ 2a и 0 в режиме С++ 17. Строка GCC фактически оценивает это значение 1 в С++ 17, но также обрабатывает __VA_OPT__
в этом режиме.
Для этого используется BOOST_PP_OVERLOAD
, чтобы вызвать версию _1
или _2
_II
на основе количества аргументов. Если __VA_OPT__(,)
расширяется до ,
, будет два пустых аргумента. Если нет, будет один пустой аргумент. Мы всегда вызываем этот макрос с помощью списка аргументов, поэтому любой компилятор, поддерживающий __VA_OPT__
, должен всегда расширять его до ,
.
Естественно, зависимость Boost.PP не является обязательной. Простой макрос 1 или 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 есть предупреждение о переносимости:
предупреждение: переменные макросы несовместимы с С++ 98 [-WС++ 98-compat-pedantic]
Я не знаю, возможно ли это обнаружение без поддержки переменных макросов С++ 11. Вы можете предположить, что не поддерживайте значения __cplusplus
ниже С++ 11, но Clang все еще дает предупреждение, даже если оно завернуто в такую проверку.
Ответ 3
Как уже упоминалось в другом ответе, вы можете написать свой собственный макрос OVERLOAD
. BOOST_PP_OVERLOAD
состоит из двух частей: BOOST_PP_CAT
и BOOST_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
Ответ 4
Проблема с решением, как указано в его наиболее популярном ответе выше, заключается в том, что компилятор может выдавать предупреждение или даже ошибку, если __VA_OPT__ используется вне его режима С++ 20, поскольку слово является компилятором зарезервированное слово, поскольку оно начинается и заканчивается двойным подчеркиванием. Фактически я обнаружил, что gcc выдаст предупреждение или ошибку в зависимости от используемых опций компилятора, хотя обычно это не происходит в большинстве случаев компиляции. Из-за этого окружает любое решение с текущим тестом для С++ 20, например:
# if defined(__cplusplus) && __cplusplus > 201703L
// Solution
#endif
- более консервативное решение, хотя оно ограничивает тест до С++ 20 или выше.