Как дважды объединить с препроцессором C и развернуть макрос как в «arg ## _ ## MACRO»?

152

Я пытаюсь написать программу, в которой имена некоторых функций зависят от значения определенной макропеременной с помощью макроса:

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

int NAME(some_function)(int a);

К сожалению, макрос NAME()превращает это в

int some_function_VARIABLE(int a);

скорее, чем

int some_function_3(int a);

так что это явно неправильный путь. К счастью, число различных возможных значений для VARIABLE невелико, поэтому я могу просто сделать #if VARIABLE == nи перечислить все случаи отдельно, но мне было интересно, если есть разумный способ сделать это.

JJ.
источник
3
Вы уверены, что не хотите использовать вместо них указатели на функции?
György Andrasek
8
@Jurily - указатели на функции работают во время выполнения, препроцессор работает во время (до) компиляции. Есть разница, даже если оба могут быть использованы для одной и той же задачи.
Крис Латс
1
Дело в том, что она используется в быстрой библиотеке вычислительной геометрии, которая встроена в определенное измерение. Однако иногда кому-то может понадобиться использовать его с несколькими различными измерениями (скажем, 2 и 3), и поэтому может потребоваться простой способ создания кода с именами функций и типов, зависящими от измерения. Кроме того, код написан на ANSI C, так что классные вещи C ++ с шаблонами и специализацией здесь не применимы.
Джей Джей
2
Голосование возобновлено, потому что этот вопрос касается рекурсивного расширения макросов, а stackoverflow.com/questions/216875/using-in-macros является общим «для чего он хорош». Название этого вопроса должно быть более точным.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
Хотелось бы, чтобы этот пример был минимизирован: то же самое происходит #define A 0 \n #define M a ## A: наличие двух ##не является ключом.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

Ответы:

223

Стандартный C препроцессор

$ cat xx.c
#define VARIABLE 3
#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y)  PASTER(x,y)
#define NAME(fun) EVALUATOR(fun, VARIABLE)

extern void NAME(mine)(char *x);
$ gcc -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"





extern void mine_3(char *x);
$

Два уровня косвенности

В комментарии к другому ответу Кейд Ру спросил, почему для этого нужны два уровня косвенности. Непростой ответ: потому что так требует стандарт для работы; вы также обнаружите, что вам нужен эквивалентный трюк с оператором stringizing.

Раздел 6.10.3 стандарта C99 охватывает «замену макросов», а 6.10.3.1 - «замену аргументов».

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

В вызове NAME(mine)аргумент «мой»; оно полностью расширено до «моего»; затем он подставляется в строку замены:

EVALUATOR(mine, VARIABLE)

Теперь макрос EVALUATOR обнаружен, а аргументы изолированы как «моя» и «переменная»; последний затем полностью расширяется до «3» и подставляется в строку замены:

PASTER(mine, 3)

Операция этого покрыта другими правилами (6.10.3.3 «Оператор ##»):

Если в списке замены функционально-подобного макроса параметру непосредственно предшествует или следует ##маркер предварительной обработки, этот параметр заменяется последовательностью токена предварительной обработки соответствующего аргумента; [...]

Как для объектных, так и для функциональных вызовов макросов, перед повторным исследованием списка замены для замены большего количества имен макросов каждый экземпляр ##токена предварительной обработки в списке замены (не из аргумента) удаляется, а предыдущий токен предварительной обработки объединяется. со следующим токеном предварительной обработки.

Итак, список замены содержит, xа затем ##и ##следуют y; итак имеем:

mine ## _ ## 3

и удаление ##токенов и объединение токенов с обеих сторон объединяет «мой» с «_» и «3» для получения:

mine_3

Это желаемый результат.


Если мы посмотрим на исходный вопрос, код был (адаптирован для использования 'mine' вместо 'some_function'):

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

NAME(mine)

Аргумент NAME явно «мой», и он полностью расширен.
Следуя правилам 6.10.3.3, находим:

mine ## _ ## VARIABLE

который, когда ##операторы исключены, отображается на:

mine_VARIABLE

именно так, как сообщается в вопросе.


Традиционный препроцессор C

Роберт Рюгер спрашивает :

Есть ли способ сделать это с традиционным препроцессором C, который не имеет оператора вставки токена ##?

Возможно, а может и нет - это зависит от препроцессора. Одно из преимуществ стандартного препроцессора состоит в том, что он имеет эту функцию, которая работает надежно, тогда как для предстандартных препроцессоров были разные реализации. Одно требование состоит в том, что, когда препроцессор заменяет комментарий, он не генерирует пробел, как это требуется для препроцессора ANSI. Препроцессор GCC (6.3.0) C отвечает этому требованию; препроцессор Clang из XCode 8.2.1 этого не делает.

Когда это работает, это делает работу ( x-paste.c):

#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

Обратите внимание, что между fun,и VARIABLE- нет пробела , это важно, потому что, если оно присутствует, оно копируется в вывод, и в результате вы mine_ 3получаете имя, которое, конечно, не является синтаксически допустимым. (Теперь, пожалуйста, можно мне вернуть волосы?)

С GCC 6.3.0 (работает cpp -traditional x-paste.c) я получаю:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_3(char *x);

С Clang из XCode 8.2.1 я получаю:

# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2





extern void mine _ 3(char *x);

Эти пространства все портят. Я отмечаю, что оба препроцессора верны; разные предстандартные препроцессоры демонстрировали оба поведения, что делало вставку токенов чрезвычайно раздражающим и ненадежным процессом при попытке переноса кода. Стандарт с ##обозначениями радикально упрощает это.

Там могут быть другие способы сделать это. Однако это не работает:

#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

GCC генерирует:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_VARIABLE(char *x);

Близко, но без игры в кости. YMMV, конечно, в зависимости от используемого вами стандартного препроцессора. Честно говоря, если вы застряли с препроцессором, который не взаимодействует, вероятно, было бы проще организовать использование стандартного препроцессора C вместо предстандартного (обычно есть способ настроить компилятор соответствующим образом), чем тратить много времени, пытаясь найти способ сделать работу.

Джонатан Леффлер
источник
1
Да, это решает проблему. Я знал трюк с двумя уровнями рекурсии - я должен был поиграть со струнностью хотя бы один раз - но не знал, как это сделать.
Джей Джей
Есть ли способ сделать это с традиционным препроцессором C, который не имеет оператора вставки токена ##?
Роберт Рюгер
1
@ RobertRüger: это удваивает длину ответа, но я добавил информацию для покрытия cpp -traditional. Обратите внимание, что нет однозначного ответа - это зависит от вашего препроцессора.
Джонатан Леффлер
Большое спасибо за ответ. Это совершенно здорово! Тем временем я также нашел другое, немного другое решение. Смотрите здесь . У него также есть проблема в том, что он не работает с Clang. К счастью, это не проблема для моего заявления ...
Роберт Рюгер
32
#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)

int NAME(some_function)(int a);

Честно говоря, вы не хотите знать, почему это работает. Если вы знаете, почему это работает, вы станете тем парнем на работе, который знает подобные вещи, и все придут, чтобы задать вам вопросы. знак равно

Изменить: если вы действительно хотите знать, почему это работает, я с удовольствием выложу объяснение, предполагая, что никто не побьет меня.

Стивен Кэнон
источник
Не могли бы вы объяснить, почему для этого нужны два уровня косвенности. У меня был ответ с одним уровнем перенаправления, но я удалил ответ, потому что мне пришлось установить C ++ в Visual Studio, и тогда он не работал.
Cade Roux