Может ли макрос препроцессора расширить только некоторые вставленные параметры? - PullRequest
0 голосов
/ 28 февраля 2019

Я знаю, что при расширении функционально-подобного макроса препроцессора токены # и ## в списке подстановок верхнего уровня по существу действуют "перед" любым расширением макроса в аргументе.Например, если

#define CONCAT_NO_EXPAND(x,y,z) x ## y ## z
#define EXPAND_AND_CONCAT(x,y,z) CONCAT_NO_EXPAND(x,y,z)
#define A X
#define B Y
#define C Z

, то CONCAT_NO_EXPAND(A,B,C) - это pp-токен ABC, а EXPAND_AND_CONCAT(A,B,C) - это pp-токен XYZ.

Но что, если я захочуопределить макрос, который расширяет только некоторые из его аргументов перед вставкой?Например, я хотел бы макрос, который позволяет раскрывать только середину из трех аргументов, а затем вставляет его вместе с точным нерасширенным префиксом и точным нерасширенным суффиксом, даже если префикс или суффикс является идентификатором объектоподобного макроса.То есть, если снова у нас будет

#define MAGIC(x,y,z) /* What here? */
#define A X
#define B Y
#define C Z

, тогда MAGIC(A,B,C) будет AYC.

Простая попытка, подобная

#define EXPAND(x) x
#define MAGIC(x,y,z) x ## EXPAND(y) ## z

, приводит к ошибке вставки«)» и «C» не дают действительный токен предварительной обработки ». Это имеет смысл (и я предполагаю, что он также производит нежелательный токен AEXPAND).

Есть ли способ получитьтакого рода результат с использованием только стандартных переносимых правил препроцессора? (Никаких дополнительных инструментов генерации или изменения кода).

Если нет, может быть, способ, который работает в большинстве распространенных реализаций? Здесь Boost.PP будет справедливымигра, даже если она включает в себя некоторые специфические для компилятора приемы или обходные пути.

Если это имеет какое-то значение, меня больше всего интересуют шаги препроцессора, определенные в C ++ 11 и C ++ 17.

1 Ответ

0 голосов
/ 28 февраля 2019

Вот решение:

#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).

...