简体   繁体   中英

How to pass multi-argument templates to macros?

Say I have a macro like this:

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

This won't work if I pass it a template that has more than one parameter, because the comma in the <int, int> is interpreted as separating the macro arguments, not the template arguments.

SET_TYPE_NAME(std::map<int, int>, "TheMap")
// Error: macro expects two arguments, three given

This problem seems to be solved by doing this:

SET_TYPE_NAME((std::map<int, int>), "TheMap")

But now another problem arises, one that I really did not expect:

 template<>
 std::string name<(std::map<int, int>)>()
 // template argument 1 is invalid

It seems that the extra parentheses make the template argument invalid. Is there any way around this?

Besides typedef , you could switch the order of the arguments and use variadic macros (requires C99 or C++11-compatible compiler):

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

You could use typedef :

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

SET_TYPE_NAME(int_map, "TheMap");

boost's BOOST_FOREACH suffers from the same issue.

Some time ago (while searching the web for the utility of Identity<T> ) I got to this page .

In brief, to answer the question, if you don't have C++11 support and/or can't (or don't want to) use a typedef , you call your macro the "right" way:

// 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")

and then to get rid of the (possible) unwanted parentheses around the type , you can use a "helper" like this:

template<typename> struct RemoveBrackets;
template<typename T> struct RemoveBrackets<void (T)> {
    typedef T Type;
};

and in your macro change the line:

    std::string name<TYPE>() {    \

to:

    std::string name< RemoveBrackets<void (TYPE)>::Type >() {    \

(or define a helper macro, say

#define REMOVE_BRACKETS(x) RemoveBrackets<void (x)>::Type

then replace the line with

    std::string name< REMOVE_BRACKETS(TYPE) >() {    \

).

(For the full story read the paragraph "An even better solution" at the end of the article linked above.)

Edit: just found that . But it uses <void X> when it really should use <void (X)> (in get_first_param<void X>::type ); indeed the parentheses are necessary if you pass a "simple", non-bracketed argument (like SomeType2 in my code above) -- and they don't hurt if X is already bracketed (eg, void ((SomeType1)) is equivalent to void (SomeType1) ; again, see the article). (By the way, I noticed that many answers on the other SO page are in essence "Macros are dumb". I won't comment, though.)

I'd like to add an answer to my own question. Now that Boost 1.50.0 was released, Boost.Utility has a new mini-library that helps with this kind of thing. If you look at the source you'll see it's implemented the same as gx_'s solution . Here's how to use it:

#include <boost/utility/identity_type.hpp>
SET_TYPE_NAME(BOOST_IDENTITY_TYPE((std::map<int, int>)), "TheMap");

I like the typedef way proposed by hmjd better, but for the record, the usual way I've seen around this is to kick the angle brackets out of the macro and write:

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

Usage:

SET_TYPE_NAME(<std::map<int, int> >, "TheMap")

This is a variation of an old technique used for error message reporting and fprintf :

#define Error(args) do { \
    printf("ERROR: "); \
    printf args; \
    printf("\n"); \
    return 1; \
    } while(0)

Called with:

Error(("Index out of range: %d not in %d ... %d.", var, min, max));

It's ugly but it worked. Useful if the coding style rules ban typedef .

A more generic way is to always use () around your arguments that may contain a comma and use CONCAT to remove the parenthesis. If you do so, you can define multiple parameters packs wits comma inside or put arguments in your favorite order

#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>) )
typedef std::map<int,int> IntMap_t;
SET_TYPE_NAME(IntMap_t, "TheMap")

You can declare a typedef and use it in the macro

I know this is an old question. Though this comes in top results on a Google search and nobody has yet mentioned an even more simple solution by defining a macro for the comma:

#define COMMA ,
SET_TYPE_NAME(std::map<int COMMA int>, "TheMap")

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM