Как передать шаблоны с несколькими аргументами в макросы? - PullRequest
12 голосов
/ 20 января 2012

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

#define SET_TYPE_NAME(TYPE, NAME) \
    template<typename T>          \
    std::string name();           \
                                  \
    template<>                    \
    std::string name<TYPE>() {    \
        return NAME;              \
    }

Это не сработает, если я передам ему шаблон, имеющий более одного параметра, потому что запятая в <int, int> интерпретируется как разделяющая аргументы macro , а не аргументы шаблона.

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

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

Ответы [ 7 ]

9 голосов
/ 20 января 2012

Помимо typedef, вы можете переключать порядок аргументов и использовать макросы с переменным числом (требуется компилятор, совместимый с C99 или C ++ 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>)
8 голосов
/ 20 января 2012

Вы можете использовать typedef:

typedef std::map<int, int> int_map;

SET_TYPE_NAME(int_map, "TheMap");

boost's BOOST_FOREACH страдает от той же проблемы.

6 голосов
/ 23 июля 2012

Некоторое время назад (при поиске в сети утилиты Identity<T>) я попал на эту страницу .

Вкратце, чтобы ответить на вопрос, если у вас нет поддержки C ++ 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 голосов
/ 28 июля 2012

Я бы хотел добавить ответ на свой вопрос.Теперь, когда Boost 1.50.0 был выпущен, Boost.Utility имеет новую мини-библиотеку, которая помогает с такими вещами.Если вы посмотрите на источник, то увидите, что он реализован так же, как и решение gx_ .Вот как это использовать:

#include <boost/utility/identity_type.hpp>
SET_TYPE_NAME(BOOST_IDENTITY_TYPE((std::map<int, int>)), "TheMap");
3 голосов
/ 20 января 2012

Мне больше нравится способ определения типа, предложенный 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.

1 голос
/ 12 декабря 2013

Более общий способ - всегда использовать () вокруг аргументов, которые могут содержать запятую, и использовать 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>) )
1 голос
/ 20 января 2012
typedef std::map<int,int> IntMap_t;
SET_TYPE_NAME(IntMap_t, "TheMap")

Вы можете объявить typedef и использовать его в макросе

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...