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