Как передать шаблоны с несколькими аргументами в макросы?
Скажем, у меня такой макрос:
#define SET_TYPE_NAME(TYPE, NAME) \
template<typename T> \
std::string name(); \
\
template<> \
std::string name<TYPE>() { \
return NAME; \
}
Это не сработает, если я передам ему шаблон с более чем одним параметром, потому что запятая в <int, int>
интерпретируется как разделение аргументов макроса, а не аргументов шаблона.
SET_TYPE_NAME(std::map<int, int>, "TheMap")
// Error: macro expects two arguments, three given
Эта проблема, кажется, решается, делая это:
SET_TYPE_NAME((std::map<int, int>), "TheMap")
Но теперь возникает другая проблема, которую я действительно не ожидал:
template<>
std::string name<(std::map<int, int>)>()
// template argument 1 is invalid
Похоже, что дополнительные круглые скобки делают аргумент шаблона недействительным. Есть ли способ обойти это?
Ответы
Ответ 1
Помимо typedef
, вы можете переключить порядок аргументов и использовать переменные макросы (требуется C99 или С++ 11-совместимый компилятор):
#define SET_TYPE_NAME(NAME, ...) \
template<typename T> \
std::string name(); \
\
template<> \
std::string name<__VA_ARGS__>() { \
return NAME; \
}
...
SET_TYPE_NAME("TheMap", std::map<int, int>)
Ответ 2
Вы можете использовать typedef
:
typedef std::map<int, int> int_map;
SET_TYPE_NAME(int_map, "TheMap");
boost BOOST_FOREACH имеет ту же проблему.
Ответ 3
Некоторое время назад (при поиске в Интернете для утилиты Identity<T>
) я добрался до этой страницы.
Вкратце, чтобы ответить на вопрос, если у вас нет поддержки на С++ 11 и/или не можете (или не хотите) использовать typedef
, вы вызываете свой макрос "правильным", способ:
// If an argument contains commas, enclose it in parentheses:
SET_TYPE_NAME((std::map<int, int>), "TheMap")
// For an argument that doesn't contain commas, both should work:
SET_TYPE_NAME((SomeType1), "TheType1")
SET_TYPE_NAME(SomeType2, "TheType2")
а затем , чтобы избавиться от (возможных) нежелательных круглых скобок вокруг типа, вы можете использовать "помощник" следующим образом:
template<typename> struct RemoveBrackets;
template<typename T> struct RemoveBrackets<void (T)> {
typedef T Type;
};
и в вашем макросе измените строку:
std::string name<TYPE>() { \
в
std::string name< RemoveBrackets<void (TYPE)>::Type >() { \
(или определить вспомогательный макрос, скажем
#define REMOVE_BRACKETS(x) RemoveBrackets<void (x)>::Type
затем замените строку на
std::string name< REMOVE_BRACKETS(TYPE) >() { \
).
(В полной версии читайте абзац "Еще лучшее решение" в конце статьи, приведенной выше.)
Изменить: только что нашел который. Но он использует <void X>
, когда он действительно должен использовать <void (X)>
(в get_first_param<void X>::type
); действительно, круглые скобки необходимы, если вы передаете "простой" аргумент без привязки (например, SomeType2
в моем коде выше) - и они не пострадают, если X
уже заключен в скобки (например, void ((SomeType1))
эквивалентен до void (SomeType1)
, снова см. статью). (Кстати, я заметил, что многие ответы на другой странице SO по существу "Макросы тупые", но я не буду комментировать.)
Ответ 4
Я хотел бы добавить ответ на свой вопрос. Теперь, когда Boost 1.50.0 был выпущен, Boost.Utility имеет новую мини-библиотеку, которая помогает в этом. Если вы посмотрите на источник, вы увидите, что он реализован так же, как gx_ solution. Вот как его использовать:
#include <boost/utility/identity_type.hpp>
SET_TYPE_NAME(BOOST_IDENTITY_TYPE((std::map<int, int>)), "TheMap");
Ответ 5
Мне нравится способ typedef, предложенный hmjd лучше, но для записи обычный способ, который я видел вокруг этого, - выбить угловые скобки из макроса и написать:
#define SET_TYPE_NAME(TYPE, NAME) \
template<typename T> \
std::string name(); \
\
template<> \
std::string name TYPE() { \
return NAME; \
}
Использование:
SET_TYPE_NAME(<std::map<int, int> >, "TheMap")
Это вариант старого метода, используемого для сообщения сообщений об ошибках, и fprintf
:
#define Error(args) do { \
printf("ERROR: "); \
printf args; \
printf("\n"); \
return 1; \
} while(0)
Вызывается с помощью
Error(("Index out of range: %d not in %d ... %d.", var, min, max));
Это некрасиво, но это сработало. Полезно, если правила стиля кодирования запрещают typedef
.
Ответ 6
typedef std::map<int,int> IntMap_t;
SET_TYPE_NAME(IntMap_t, "TheMap")
Вы можете объявить typedef и использовать его в макросе
Ответ 7
Более общий способ - всегда использовать() вокруг ваших аргументов, которые могут содержать запятую, и использовать CONCAT для удаления скобок. Если вы это сделаете, вы можете определить несколько пачек параметров, задуманных запятой внутри, или поместить аргументы в ваш любимый порядок.
#ifndef CONCAT
#define CONCAT __VA_ARGS__
#endif
#define MYMACRO(tparam,classname)\
template < CONCAT tparam >\
class CONCAT classname {};
//create a template class X<T,U>
MYMACRO( (typename T,typename U) , (X<T,U>) )