Частично то, что усложняет препроцессор C, заключается в том, что он требует рекурсии в группировки в скобках. На практике это означает, что либо нам нужно применять задержки / оценки на внешних уровнях, либо нам нужны разные макросы на внутренних. Также существует асимметрия в спецификации на уровне рекурсии, которая, хотя и не сложна сама по себе, должна быть учтена.
Если необходимо, я могу предположить максимальную глубину вложенности в скобках, например четыре уровня.
Я согласен на это ... и поддержу четыре уровня.
Определения
Определения (с использованием терминов, совместимых с препроцессором ускорения): A кортеж - это структура данных предварительной обработки, состоящая из одной группы в скобках, элементы которой разделены запятыми. Итак, (a,(b,c),d)
- это кортеж с тремя элементами ... a
, (b,c)
и d
. Последовательность - это структура данных предварительной обработки, состоящая из серии смежных элементов, каждый из которых заключен в группу в скобках. Таким образом, те же элементы, перечисленные выше, представлены последовательностью (a)((b,c))(d)
.
Дизайн
Для обработки кортежей с использованием вариативности нам нужен счетчик, связующий макрос и макрос для каждого из размеры кортежей, которые мы хотим поддерживать. Если, скажем, мы поддерживаем кортежи от 1 до 10 элементов и хотим изменить его для поддержки кортежей от 1 до 20, нам, возможно, придется изменить счетчик и добавить еще 10 макросов. Но если мы поддерживаем 4 уровня рекурсии с использованием одних только кортежей, нам потребуется в 4 раза больше макросов; это делает масштабирование более обременительным.
Вы показываете две формы макросов; оба используют кортежи, последний просто использует последовательность на верхнем уровне. Для единообразия я выберу первую форму. Но для масштабируемости я добавлю обработку кортежей только для преобразования кортежа в последовательность, показывая поддержку кортежей из 9 элементов ... затем, чтобы поддерживать кортежи из n элементов, вам нужно только изменить счетчик и добавить макросы n-9.
Подход
Мы начинаем с базового c связующего макроса, счетчика и преобразователя последовательности на одном уровне (PARN
):
#define GLUE(A,B) GLUE_I(A,B)
#define GLUE_I(A,B) A##B
#define PARN(...) GLUE(PARN_, COUNT (__VA_ARGS__)) (__VA_ARGS__)
#define PARN_9(A,B,C,D,E,F,G,H,I) (A)(B)(C)(D)(E)(F)(G)(H)(I)
...
#define PARN_2(A,B) (A)(B)
#define PARN_1(A) (A)
Мы хотите либо перебрать другой уровень, либо просто обработать элемент в зависимости от того, заключен ли он в скобки. Для этого я буду применять сопоставление с образцом на основе «косвенного второго макроса». Идея состоит в том, что вы помещаете шаблон в аргумент 1 и настраиваете макросы таким образом, чтобы, если шаблон совпадает, он сдвигал токены в аргумент 2; в противном случае ваш шаблон просто создает кучу токенов в аргументе 1, которые игнорируются. Вот конструкция сопоставления шаблонов с детектором в скобках (обратите внимание, что мой второй расширяется до пустого, если вы передаете ему 1 аргумент):
#define SECOND(...) SECOND_I(__VA_ARGS__,,)
#define SECOND_I(A,B,...) B
#define CALLDETECT(...) ,
Учитывая предоставленную вами ссылку, вы уже должны знать, как применять последовательности. У меня есть специальный макрос c для приложения последовательности:
#define PASTE_E(...) PASTE_E_I(__VA_ARGS__)
#define PASTE_E_I(...) __VA_ARGS__ ## E
... типичная «простая» обработка последовательности переключается между двумя макросами, называйте их A
и B
, пока они не попадут в терминал ... поэтому они go A, B, A, B, ..., E. Но мы хотим оставить след в виде результатов, разделенных запятыми, без лишних запятых. Итак, мы добавим запятую в каждый из этих макросов, но начнем с другого A, которое не добавляется; т.е. мы идем к go A, B, C, B, C, ..., E. Нам понадобятся четыре из этих наборов, чтобы (а) избежать синей краски и (б) обработать иначе на верхнем уровне. Вот начальный набор:
#define BRACP0(X) {SECOND(CALLDETECT X MAGIC1)X}
#define BRAC0_A(X) BRACP0(X)BRAC0_B
#define BRAC0_B(X) ,BRACP0(X)BRAC0_C
#define BRAC0_C(X) ,BRACP0(X)BRAC0_B
#define BRAC0_BE
#define BRAC0_CE
Это привяжет внешний уровень MAGIC
к внутреннему уровню MAGIC1
; этот набор специально добавляет дополнительную {}
пару на SP c (BRACP0
). В остальном наборы похожи, за исключением {}
. Но для последнего уровня мы хотим вызвать простую bracify вместо «next magi c» (это тоже масштабируемое; вам нужен один из этих наборов для каждой глубины рекурсии уровня). Bracify и BRACP уровня 4 выглядят так:
#define BRACIFY(...) {__VA_ARGS__}
#define BRACP4(X) SECOND(CALLDETECT X BRACIFY)X
У меня есть «развертка» для каждого мага c макроуровень:
#define MAGIC(...) {PASTE_E(MAGIC_U(BRAC0_A PARN(__VA_ARGS__)))}
#define MAGIC_U(...) __VA_ARGS__
#define MAGIC1(...) {PASTE_E(MAGIC1_U(BRAC1_A PARN(__VA_ARGS__)))}
#define MAGIC1_U(...) __VA_ARGS__
...
#define MAGIC4(...) {PASTE_E(MAGIC4_U(BRAC3_A PARN(__VA_ARGS__)))}
#define MAGIC4_U(...) __VA_ARGS__
Полное решение и демонстрация
Ссылка на coliru.stacked-crooked demo.
Заключительные комментарии
Я интерпретировал здесь C99 как имеющий значение стандарт, а этот вопрос - как просто «возможно ли»; поэтому я не стал преобразовывать это для работы с препроцессором MSVS. Вероятно, не будет работать с как есть. Сообщите мне, если это вас вообще беспокоит.