Як двічі об'єднатися з препроцесором 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і перерахувати всі випадки окремо, але мені було цікаво, чи є розумний спосіб це зробити.


3
Ви впевнені, що замість цього не хочете використовувати покажчики функцій?
Дьорджі Андрасек

8
@Jurily - Функціональні покажчики працюють під час виконання, препроцесор працює під час (до) компіляції. Існує різниця, навіть якщо обидва можуть бути використані для одного завдання.
Кріс Лутц

1
Справа в тому, що для цього використовується швидка бібліотека обчислювальної геометрії .. яка є провідним для певного виміру. Однак іноді комусь захочеться використовувати його з кількома різними розмірами (скажімо, 2 і 3), і тому потрібен буде простий спосіб генерування коду із залежними від розміру функціями та назвами типів. Також код написаний в ANSI C, тому фанк-матеріал C ++ із шаблонами та спеціалізацією тут не застосовується.
JJ.

2
Голосування за повторне відкриття, оскільки це питання є специфічним щодо рекурсивного розширення макросів та stackoverflow.com/questions/216875/using-in-macros - це загальне "для чого це добре". Заголовок цього питання слід зробити більш точним.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Я б хотів, щоб цей приклад був зведений до мінімуму: те ж саме відбувається і з #define A 0 \n #define M a ## A: два ##не є ключовим.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Відповіді:


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);
$

Два рівні непрямості

У коментарі до іншої відповіді Кейд Ру запитав, для чого потрібні два рівні непрямості. Відверта відповідь тому, що саме так стандарт вимагає, щоб він працював; ви схильні знаходити, що вам потрібен рівноцінний трюк і з оператором струнізації.

Розділ 6.10.3 стандарту C99 охоплює "заміну макросів", а 6.10.3.1 - "заміну аргументів".

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

У виклику NAME(mine)аргумент є "мій"; вона повністю розширена на «моє»; Потім він заміщується в рядок заміни:

EVALUATOR(mine, VARIABLE)

Тепер макрос ОЦІНЮВАННЯ виявлено, а аргументи виділяються як "моє" та "ЗМІННЕ"; остання потім повністю розширюється до '3' і замінюється в рядок заміни:

PASTER(mine, 3)

Дія цього регулюється іншими правилами (6.10.3.3 'Оператор ##'):

Якщо у списку заміни функціонального макросу параметру негайно передує або слідує ##маркер попередньої обробки, параметр заміняється відповідною послідовністю лексеми попередньої обробки аргументу; [...]

Як для об'єктних, так і функціональних викликів макросів, перед тим, як список заміни буде переглянуто, щоб замінити більше імен макросів, кожен екземпляр ##маркера попередньої обробки зі списку заміни (не з аргументу) видаляється, а попередній маркер попередньої обробки об'єднується із наступним маркером попередньої обробки.

Отже, список заміни містить, xза яким слідує, ##а також ##слідує y; тому у нас є:

mine ## _ ## 3

і усунення ##жетонів і об'єднання лексем з обох сторін поєднує в собі "міну" з "_" і "3", щоб отримати:

mine_3

Це бажаний результат.


Якщо ми подивимось на початкове запитання, код був (пристосований для використання "мій" замість "деякої функції"):

#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
Так, це вирішує проблему. Я знав трюк з двома рівнями рекурсії - мені довелося хоч раз грати з струфікацією - але не знав, як це зробити.
JJ.

Чи є спосіб зробити це з традиційним препроцесором C, який не має оператора вставки лексеми ##?
Роберт Рюгер

1
@ RobertRüger: це подвоює тривалість відповіді, але я додав інформацію для висвітлення cpp -traditional. Зауважте, що остаточної відповіді немає - це залежить від попереднього процесу.
Джонатан Леффлер

Дуже дякую за відповідь. Це абсолютно чудово! Тим часом я знайшов ще одне, трохи інше рішення. Дивіться тут . У нього також є проблема, що він не працює з клангом. На щастя, це не проблема для моєї заяви ...
Роберт Рюгер

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, і тоді вона не працюватиме.
Кейд Ру
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.