Кома в макросі C / C ++


103

Скажімо, у нас є такий макрос

#define FOO(type,name) type name

Якими ми могли б користуватися

FOO(int, int_var);

Але не завжди так просто:

FOO(std::map<int, int>, map_var); // error: macro "FOO" passed 3 arguments, but takes just 2

Звичайно, ми могли б зробити:

 typedef std::map<int, int> map_int_int_t;
 FOO(map_int_int_t, map_var); // OK

що не дуже ергономічно. Плюс несумісність має бути вирішена. Будь-яка ідея, як вирішити це за допомогою макросу?


Я здогадуюсь, що вам доведеться уникати символів із значенням, щоб зробити їх буквальними.
Джіт

Принаймні, на C ++ ви можете поставити typedef де завгодно, тому я не впевнений, чому ви говорите, що це повинно бути "заздалегідь".
Вон Катон

Відповіді:


108

Оскільки кутові дужки можуть також представляти (або відбуваються в) оператори порівняння <, >, <=і >=, макро розширення не може ігнорувати коми всередині кутові дужки , як це відбувається в круглих дужках. (Це також проблема квадратних дужок і дужок, навіть якщо вони зазвичай бувають врівноваженими парами.) Макро аргумент можна вкласти в круглі дужки:

FOO((std::map<int, int>), map_var);

Проблема полягає в тому, що параметр залишається в скобках усередині макророзширення, що не дозволяє його читати як тип у більшості контекстів.

Приємний фокус для вирішення цього полягає в тому, що в C ++ ви можете витягти ім'я типу з імені типу в скобках, використовуючи тип функції:

template<typename T> struct argument_type;
template<typename T, typename U> struct argument_type<T(U)> { typedef U type; };
#define FOO(t,name) argument_type<void(t)>::type name
FOO((std::map<int, int>), map_var);

Оскільки формуючі типи функцій ігнорують додаткові дужки, ви можете використовувати цей макрос із дужками або без них, де ім'я типу не містить кома:

FOO((int), int_var);
FOO(int, int_var2);

Звичайно, це не обов'язково, оскільки імена типів не можуть містити коми поза дужок. Отже, для міжмовного макросу ви можете написати:

#ifdef __cplusplus__
template<typename T> struct argument_type;
template<typename T, typename U> struct argument_type<T(U)> { typedef U type; };
#define FOO(t,name) argument_type<void(t)>::type name
#else
#define FOO(t,name) t name
#endif

Це круто. Але як ви дізналися про це? Я пробував багато хитрощів і навіть не думав, що тип функції вирішить проблему.
Буде опікун

@WilliamCustode, наскільки я пам’ятаю, я вивчав граматику типів функцій та декларацій функцій з посиланням на найбільш загрозливу проблему розбору, тому мені випадково було відомо, що надлишкові дужки можуть бути застосовані до типу в цьому контексті.
ecatmur

Я знайшов проблему з цим методом під час роботи з шаблонами. Скажімо, потрібний мені такий код: template<class KeyType, class ValueType> void SomeFunc(FOO(std::map<KeyType, ValueType>) element) {}Якщо я застосую тут це рішення, структури, що стоять за макросом, стають залежними типами, і тепер для цього типу потрібен префікс імені типу. Ви можете додати його, але виведення типу було порушено, тому тепер вам доведеться вручну перераховувати аргументи типу, щоб викликати функцію. Я в кінцевому підсумку використовував метод temple для визначення макросу для коми. Це може виглядати не дуже красиво, але воно спрацювало чудово.
Роджер Сандерс

Невелика проблема у відповіді: В ньому йдеться про те, що коми ігноруються всередині, [] і {}це не так, це працює лише ()сумно. Дивіться: Однак, немає жодних вимог до вирівнювання квадратних дужок або дужок ...
VinGarcia

На жаль, це не працює в MSVC: godbolt.org/z/WPjYW8 . Здається, MSVC не дозволяє додавати декілька паролів і не може їх розібрати. Рішення , яке не настільки елегантний , але швидше (менше конкретизація шаблону), щоб обернути кому вид аргументу в обгортці макро: #define PROTECT(...) argument_type<void(__VA_ARGS__)>::type. Передача аргументів тепер легко можлива навіть через декілька макросів, а для простих типів можна опустити PROTECT. Однак типи функцій стають покажчиками функцій при такій
оцінці

119

Якщо ви не можете використовувати дужки, і вам не подобається рішення SINGLE_ARG Майка, просто визначте COMMA:

#define COMMA ,

FOO(std::map<int COMMA int>, map_var);

Це також допомагає, якщо ви хочете впорядкувати деякі аргументи макросу, як у

#include <cstdio>
#include <map>
#include <typeinfo>

#define STRV(...) #__VA_ARGS__
#define COMMA ,
#define FOO(type, bar) bar(STRV(type) \
    " has typeid name \"%s\"", typeid(type).name())

int main()
{
    FOO(std::map<int COMMA int>, std::printf);
}

який друкує std::map<int , int> has typeid name "St3mapIiiSt4lessIiESaISt4pairIKiiEEE".


16
#define COMMA Ух, ти щойно врятував мені ГОЛОВНУ роботу ... чому я не придумав цього років тому. Дякуємо, що поділилися цією ідеєю. Це навіть дозволяє мені створювати макроси, функції настройки з різними аргументами рахуються взагалі.
моліад

28
Плюс 1 до жаху
namezero

1
@kiw Якщо #define STRVX(...) STRV(__VA_ARGS__)і #define STRV(...) # __VA_ARGS__, то std::cout << STRV(type<A COMMA B>) << std::endl;надрукує type<A COMMA B>і std::cout << STRVX(type<A COMMA B>) << std::endl;буде друкувати type<A , B>. ( STRVпризначено для "variadic stringify", а STRVXтакож для "розширеної варіативної stringify".)
не користувач

1
@ не-користувач так, але макроси з різними варіантами вам не потрібні COMMAв першу чергу. На цьому я і закінчився.
kiw

Я б ніколи цього не використовував, але +1 за те, що був смішним.
Рафаель Баптиста

58

Якщо ваш препроцесор підтримує різні макроси:

#define SINGLE_ARG(...) __VA_ARGS__
#define FOO(type,name) type name

FOO(SINGLE_ARG(std::map<int, int>), map_var);

Інакше це трохи стомливіше:

#define SINGLE_ARG2(A,B) A,B
#define SINGLE_ARG3(A,B,C) A,B,C
// as many as you'll need

FOO(SINGLE_ARG2(std::map<int, int>), map_var);

О, чорт ... Чому? Чому б не просто укласти в дужки?

15
@VladLazarenko: Тому що не завжди можна поставити довільні фрагменти коду в дужки. Зокрема, ви не можете розміщувати дужки навколо імені типу в деклараторі, саме таким стає цей аргумент.
Майк Сеймур

2
... а також тому, що ви можете лише змінювати визначення макросу, а не всі місця, які його викликають (які можуть не бути під вашим контролем, або можуть бути розповсюджені на 1000 тисяч файлів тощо). Це відбувається, наприклад, при додаванні макросу для прийняття на себе обов'язків з функції, названої як
BeeOnRope

32

Просто визначте FOOяк

#define UNPACK( ... ) __VA_ARGS__

#define FOO( type, name ) UNPACK type name

Потім завжди викликайте круглі дужки навколо аргументу типу, наприклад

FOO( (std::map<int, int>), map_var );

Звичайно, може бути хорошою ідеєю приклади викликів у коментарі до визначення макросу.


Не впевнений, чому це так далеко вниз, це набагато приємніше рішення, ніж Майк Сеймурс. Це швидко і просто і повністю приховано від користувача.
iFreilicht

3
@iFreilicht: Це було опубліковано трохи більше року. ;-)
Ура та хт. - Альф

5
А тому що також важко зрозуміти, як і чому це працює
VinGarcia

@VinGarcia, ви можете пояснити, чому / як це працює? Чому для його виклику потрібні дужки? Що UNPACKробити при такому використанні ) UNPACK type name? Чому typeправильно використовується тип при використанні ) UNPACK type name? Тільки що, чорт тут, відбувається?
користувач

Ні @user, можливо, Cheers and hth можуть відповісти вам
VinGarcia

4

Існують як мінімум два способи зробити це. Спочатку ви можете визначити макрос, який приймає кілька аргументів:

#define FOO2(type1, type2, name) type1, type2, name

якщо ви зробите це, ви можете виявити, що в кінці кінців визначаєте більше макросів для обробки більшої кількості аргументів.

По-друге, навколо аргументу можна поставити круглі дужки:

#define FOO(type, name) type name
F00((std::map<int, int>) map_var;

якщо це зробити, ви можете виявити, що додаткові дужки накручують синтаксис результату.


Для першого рішення кожен макрос повинен мати іншу назву, оскільки макроси не перевантажують. І по-друге, якщо ви передаєте ім'я типу, є дуже хороший шанс, що він буде використаний для оголошення змінної (або typedef), тому круглі дужки спричинить проблеми.
Джеймс Канзе

4

Це можливо з P99 :

#include "p99/p99.h"
#define FOO(...) P99_ALLBUTLAST(__VA_ARGS__) P99_LAST(__VA_ARGS__)
FOO()

Наведений вище код ефективно знімає лише останню кома у списку аргументів. Перевірте clang -E(P99 вимагає компілятора C99).


3

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


2
(^...^)це одне щасливе обличчя :)
CygnusX1,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.