Портативно обнаружить поддержку __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 или выше.