Вот подход, который на самом деле делает то, что вы просили ... Я предопределил это для работы с 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