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?
For portability, you should not have more than 31 parameters for a macro. The parameter list may end with an ellipsis (...).
Can there be more than one argument to templates? Yes, like normal parameters, we can pass more than one data type as arguments to templates.
macro (array[x = y, x + 1]) passes two arguments to macro : array[x = y and x + 1] .
Template classes and functions can make use of another kind of template parameter known as a non-type parameter. A template non-type parameter is a template parameter where the type of the parameter is predefined and is substituted for a constexpr value passed in as an argument.
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.
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>)
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 (e.g., 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.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With