Запятая в макросе C / C ++


104

Скажем, у нас есть такой макрос

#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

что не очень эргономично. Необходимо устранить несовместимость типа Plus. Есть идеи, как решить эту проблему с помощью макроса?


Я предполагаю, что вам нужно экранировать символы со смыслом, чтобы сделать их буквальными.
Jite

По крайней мере, в C ++ вы можете поместить typedef где угодно, поэтому я не уверен, почему вы говорите, что это должно быть «заранее».
Vaughn Cato

Ответы:


111

Поскольку угловые скобки могут также представлять (или происходят в) операторы сравнения <, >, <=и >=, макро расширение не может игнорировать запятые внутри угловые скобки , как это происходит в круглых скобках. (Это также проблема для квадратных и фигурных скобок, хотя они обычно встречаются как сбалансированные пары.) Вы можете заключить аргумент макроса в круглые скобки:

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

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

#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

Это круто. Но как вы узнали об этом? Я пробовал массу уловок и даже не думал, что тип функции решит проблему.
Will Custode

@WilliamCustode, насколько я помню, я изучал грамматику типов функций и объявлений функций со ссылкой на наиболее неприятную проблему синтаксического анализа, поэтому я случайно узнал, что избыточные круглые скобки могут применяться к типу в этом контексте.
ecatmur

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

Небольшая проблема с ответом: в нем указано, что запятые внутри игнорируются, [] а {}это не так, это только ()печально. См .: Тем не менее, квадратные скобки или фигурные скобки для баланса не
требуются

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

119

Если вы не можете использовать круглые скобки и вам не нравится решение Mike SINGLE_ARG, просто определите ЗАПЯТУЮ:

#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 вау, вы только что сэкономили мне ЧАСЫ работы ... почему я не подумал об этом много лет назад. Спасибо, что поделились этой идеей. Это даже позволяет мне создавать макросы, которые настраивают функции с различным количеством аргументов.
moliad 05

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означает «вариативное строковое преобразование» и STRVXозначает «расширенное вариативное строковое преобразование».)
not-a-user

1
@ not-a-user - да, но с вариативными макросами макрос вообще не нужен COMMA. Вот что у меня получилось.
кив 04

Я бы никогда не использовал это, но +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
... а также потому, что вы можете изменять только определение макроса, а не все места, которые его вызывают (которые могут не находиться под вашим контролем или могут быть распределены по тысячам файлов и т. д.). Это происходит, например, при добавлении макроса, который берет на себя функции одноименной функции.
BeeOnRope

32

Просто определите FOOкак

#define UNPACK( ... ) __VA_ARGS__

#define FOO( type, name ) UNPACK type name

Затем всегда вызывайте его с круглыми скобками вокруг аргумента типа, например

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

Конечно, может быть хорошей идеей проиллюстрировать вызовы в комментарии к определению макроса.


Не уверен, почему это так далеко, это гораздо лучшее решение, чем Майк Сеймур. Это быстро, просто и полностью скрыто от пользователя.
iFreilicht

3
@iFreilicht: Это было опубликовано чуть больше года спустя. ;-)
Ура и hth. - Альф

5
И потому что также трудно понять, как и почему это работает
VinGarcia

@VinGarcia, ты можешь объяснить, почему / как это работает? Почему при его вызове нужны скобки? Что UNPACKделать при таком использовании ) UNPACK type name? Почему typeправильно получает тип при использовании ) UNPACK type name? Что, черт возьми, здесь происходит?
пользователь

Нет @user, может быть, Cheers и 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.