Как передать шаблоны с несколькими аргументами в макросы?

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

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