Вот решение:
#define A X
#define B Y
#define C Z
#define PASTE3(q,r,s) q##r##s
#define MAGIC(x,y,z,...) PASTE3(x##__VA_ARGS__,y,__VA_ARGS__##z)
MACRO(A,B,C,)
Обратите внимание, что для вызова "требуется" другой аргумент (см. Ниже, почему);но:
MACRO(A,B,C)
здесь совместимо с C ++ 20 MACRO(A,B,C)
будет «работать» во многих препроцессорах C ++ 11 / C ++ 17 (например,gnu / clang), но это расширение, не соответствующее поведению C ++ 11 / C ++ 17
Я знаю, что при расширении функционально-подобного макроса препроцессора токены # и ##в списке подстановки верхнего уровня, по сути, действуют «до» любых расширений макросов в аргументе.
Чтобы быть более точным, есть четыре шага к раскрытию макросов:
- идентификация аргумента
- подстановка аргумента
- строковое преобразование и вставка (в неопределенном порядке)
- повторное сканирование и дальнейшая замена
Идентификация аргумента связывает параметры в определении макроса саргументы в вызове.В этом случае x
ассоциируется с A
, y
с B
, z
с C
и ...
с "меткой метки" (абстрактное пустое значение, связанное с параметром, аргумент которого не имеетжетоны).Для препроцессоров C ++ до C ++ 20 для использования ...
требуется хотя бы один параметр;поскольку в C ++ 20 добавлена функция __VA_OPT__
, использование ...
в вызове не является обязательным.
Подстановка аргумента - это шаг, на котором раскрываются аргументы.В частности, здесь происходит то, что для каждого параметра в списке замены макроса (здесь PASTE3(x##__VA_ARGS__,y,__VA_ARGS__##z)
), где указанный параметр не участвует в вставке или строковом преобразовании, связанный аргумент полностью раскрывается, как если бы он появлялся вне вызова;затем все упоминания этого параметра в списке замены, которые не участвуют в строковом преобразовании и вставке, заменяются расширенным результатом.Например, на этом шаге для вызова MAGIC(A,B,C,)
, y
является единственным упомянутым уточняющим параметром, поэтому B
расширяется, производя Y
;в этот момент мы получим PASTE3(x##__VA_ARGS__,Y,__VA_ARGS__##z)
.
На следующем шаге применяются операции вставки и строкового преобразования в произвольном порядке.Placemarker здесь нужен именно потому, что вы хотите расширить середину, а не конец, и вам не нужны дополнительные вещи;т. е. чтобы получить A
до , а не , расширить до X
, а чтобы остаться A
(в отличие от изменения на "A"
), вам нужно специально избегать подстановки аргументов.как этого избежать только двумя способами;вставка или строковое форматирование, поэтому если строковое преобразование не работает, мы должны вставить.А поскольку вы хотите, чтобы этот токен оставался таким же, как и у вас, вам нужно вставить метку (что означает, что вам нужно вставить ее, поэтому есть другой параметр).
После применения этого макросапасты для «меток», у вас получится PASTE3(A,Y,C)
;, затем - этап повторного сканирования и последующей замены, во время которого PASTE3
идентифицируется как макро-вызов.Перемотка вперед, поскольку PASTE3
вставляет свои аргументы, поскольку это не относится ни к одному из них, мы выполняем вставки в «некотором порядке» и получаем AYC
.
В качестве последнего примечания,В этом решении я использую переменный аргумент для создания маркера метки-метки именно потому, что он позволяет вызывать форму MACRO(A,B,C)
как минимум в C ++ 20.Я добавлю это к z
, потому что это делает добавление, по крайней мере, потенциально полезным для чего-то другого (MAGIC(A,B,C,_)
будет использовать _
в качестве "разделителя" для получения A_Y_C
).