Кроссплатформенный вариадический макрос с параметрами 0..n - PullRequest
0 голосов
/ 13 апреля 2019

Для пакета параметров мне нужен макрос, который может принимать любое количество параметров (фактически типов), который работает кроссплатформенно.Этот код прекрасно работает с GCC, LLVM и MSVC (после того, как препроцессор был переработан для поддержки ## последовательности (см. Поведение 4 [разделение запятыми в вариационных макросах]):

class A {};
class B: A {};
class C: A {};
class D: A {};

template<typename... Interfaces>
class Aggregator: public Interfaces... {
};

#define INNER(...) typedef Aggregator<__VA_ARGS__> AGG;
#define ENVIRONMENT(...) INNER(B, C, ## __VA_ARGS__)

ENVIRONMENT(D)

Проблема здесь в случае пустых параметров (ENVIRONMENT()). Поскольку я пока не могу использовать C ++ 20 (который поставляется с последовательностью токенов __VA_OPT__(), я должен найти решение, котороетребуется максимум C ++ 17. GCC + LLVM не имеют проблем с пустым списком параметров, однако MSVC настаивает на том, чтобы по крайней мере один параметр работал с запятой.

Что требуется, чтобы эта конструкция также работалаполностью с MSVC?

Обновление:

Оказывается, регистр пустых параметров также не работает для GCC: https://godbolt.org/z/1zKZO-.

Ответы [ 2 ]

1 голос
/ 14 апреля 2019

Вот подход, который на самом деле делает то, что вы просили ... Я предопределил это для работы с MSVC, gcc и clang (работать только с gcc и clang, или просто с MSVC, было бы проще).

Это реализует OPTIONAL, который ожидает кортеж (заключенные в скобки токены) в качестве первого аргумента.Когда OPTIONAL вызывается только с пустым вторым аргументом, он расширяется до нуля;в противном случае он расширится до развернутой версии первого аргумента.Конечный результат является своего рода аналогом для (но, конечно, не эквивалентом для) C ++ 20-х __VA_OPT__.

Ниже приведена реализация OPTIONALи поддерживаемые макросы:

#define GLUE(A,B) GLUE_C(GLUE_I,(A,B))
#define GLUE_C(A,B) A B
#define GLUE_I(A,B) A##B
#define FIRST(...) FIRST_C(FIRST_I,(__VA_ARGS__,))
#define FIRST_C(A,B) A B
#define FIRST_I(X,...) X
#define THIRD(...) THIRD_C(THIRD_CC,(THIRD_I,(__VA_ARGS__,,,)))
#define THIRD_C(A,B) A B
#define THIRD_CC(A,B) A B
#define THIRD_I(A,B,C,...) C
#define COUNT(...) COUNT_C(COUNT_I,(__VA_ARGS__,9,8,7,6,5,4,3,2,1,))
#define COUNT_C(A,B) A B
#define COUNT_I(_,_9,_8,_7,_6,_5,_4,_3,_2,X,...) X
#define DISCARD_ARGUMENTS(...)
#define OPTIONAL(APPLY_,...) \
   THIRD(GLUE(OPTIONAL_SHIFT_IF_1_IS_,COUNT(__VA_ARGS__)),\
         OPTIONAL_SINGLE_CASE,\
         APPLY_OPTION) \
   (APPLY_,__VA_ARGS__)
#define OPTIONAL_SHIFT_IF_1_IS_1 ,
#define OPTIONAL_SINGLE_CASE(APPLY_,...) \
   THIRD(OPTIONAL_SHIFT_TEST __VA_ARGS__ (0_UNLOCK), \
         DISCARD_ARGUMENTS, \
         APPLY_OPTION)(APPLY_,)
#define OPTIONAL_SHIFT_TEST(...) GLUE(OPTIONAL_APPLY_SHIFT_TEST_,FIRST(__VA_ARGS__))
#define OPTIONAL_APPLY_SHIFT_TEST_0_UNLOCK ,

#define APPLY_OPTION(A,...) APPLY_OPTION_C(APPLY_OPTION_I,A)
#define APPLY_OPTION_C(A,B) A B
#define APPLY_OPTION_I(...) __VA_ARGS__

Основной механизм - «косвенный третий макрос»;Идея здесь состоит в том, чтобы сгенерировать первый аргумент, который применяет некоторый «тест», который, если появляется что-то интересное, генерирует запятую, которая сдвигает второй аргумент на третью позицию непосредственно перед выбором.

Это используется дважды OPTIONAL;если есть один аргумент, есть следующий этап проверки, чтобы увидеть, нет ли у этого аргумента токенов.Этот тест вводит токены аргумента между OPTIONAL_SHIFT_TEST и (0_UNLOCK);если нет никаких токенов, это делает вызов, и этот макрос генерирует объектный макрос, который создает смещающую запятую.Эта косвенность является преднамеренной, позволяя использовать круглые скобки в первом аргументе без ложного обнаружения (см. Демонстрацию).

Что требуется, чтобы эта конструкция также полностью работала с MSVC?

...в косвенные слои всех макросов встроены «вызывающие макросы»;здесь все они имеют _C в имени, принимают два параметра A и B и просто расширяются до A B;они всегда используются для отделения имени макроса от набора аргументов макроса. Те адрес MSVC.Если бы я действительно пытался настроить MSVC (по какой-либо причине), был бы необходим только один такой вызывающий объект;Однако, делая вызывающую функцию для каждого набора макросов, мы получаем эту работу и для MSVC и gcc / clang.(ETA: THIRD требует двух косвенных указателей вызывающего абонента: один для переменных аргументов в самом третьем, а другой для правильной интерпретации запятых расширенного первого аргумента, поскольку в этом весь смысл макроса THIRD).

Обратите внимание, что это не зависит от каких-либо специальных трюков с запятыми.

Наконец ... с OPTIONAL все, что вам нужно сделать, это:

#define INNER(...) typedef Aggregator<__VA_ARGS__> AGG;
#define ENVIRONMENT(...) INNER(B, C OPTIONAL((,),__VA_ARGS__) __VA_ARGS__)

Godbolt demos

0 голосов
/ 13 апреля 2019

как насчет этого?

template <class... X>
using INNER = Aggregator<B, C, X...>;

#define ENVIRONMENT(...) typedef INNER<__VA_ARGS__> AGG

ENVIRONMENT(); // Or ENVIRONMENT(D)

Надеюсь, это поможет ...

...