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

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

#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

который не очень эргономичен. Следует учитывать и несовместимости типа Плюс. Любая идея, как разрешить это с помощью макроса?

Ответ 1

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

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

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

Хороший трюк для решения этого вопроса заключается в том, что в С++ вы можете извлечь имя типа из имени в скобках с использованием типа функции:

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

Ответ 2

Если вы не можете использовать круглые скобки и вам не нравится решение Mike 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".

Ответ 3

Если ваш препроцессор поддерживает переменные макросы:

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

Ответ 4

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

#define UNPACK( ... ) __VA_ARGS__

#define FOO( type, name ) UNPACK type name

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

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

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

Ответ 5

Есть как минимум два способа сделать это. Во-первых, вы можете определить макрос, который принимает несколько аргументов:

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

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

Во-вторых, вы можете поместить круг вокруг аргумента:

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

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

Ответ 6

Это возможно с помощью P99:

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

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

Ответ 7

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