Comma in C/C++ macro

2019-01-02 17:40发布

Say we have a macro like this

#define FOO(type,name) type name

Which we could use like

FOO(int, int_var);

But not always as simply as that:

FOO(std::map<int, int>, map_var); // error: macro "FOO" passed 3 arguments, but takes just 2

Of course we could do:

 typedef std::map<int, int> map_int_int_t;
 FOO(map_int_int_t, map_var); // OK

which is not very ergonomic. Plus type incompatibilities have to be dealt with. Any idea how to resolve this with macro ?

7条回答
弹指情弦暗扣
2楼-- · 2019-01-02 18:13

If your preprocessor supports variadic macros:

#define SINGLE_ARG(...) __VA_ARGS__
#define FOO(type,name) type name

FOO(SINGLE_ARG(std::map<int, int>), map_var);

Otherwise, it's a bit more tedious:

#define SINGLE_ARG2(A,B) A,B
#define SINGLE_ARG3(A,B,C) A,B,C
// as many as you'll need

FOO(SINGLE_ARG2(std::map<int, int>), map_var);
查看更多
孤独寂梦人
3楼-- · 2019-01-02 18:13

Just define FOO as

#define UNPACK( ... ) __VA_ARGS__

#define FOO( type, name ) UNPACK type name

Then invoke it always with parenthesis around the type argument, e.g.

FOO( (std::map<int, int>), map_var );

It can of course be a good idea to exemplify the invocations in a comment on the macro definition.

查看更多
旧人旧事旧时光
4楼-- · 2019-01-02 18:17

There are at least two ways to do this. First, you can define a macro that takes multiple arguments:

#define FOO2(type1, type2, name) type1, type2, name

if you do that you may find that you end up defining more macros to handle more arguments.

Second, you can put parentheses around the argument:

#define FOO(type, name) type name
F00((std::map<int, int>) map_var;

if you do that you may find that the extra parentheses screw up the syntax of the result.

查看更多
宁负流年不负卿
5楼-- · 2019-01-02 18:18

The simple answer is that you can't. This is a side effect of the choice of <...> for template arguments; the < and > also appear in unbalanced contexts so the macro mechanism couldn't be extended to handle them like it handles parentheses. (Some of the committee members had argued for a different token, say (^...^), but they weren't able to convince the majority of the problems using <...>.)

查看更多
美炸的是我
6楼-- · 2019-01-02 18:26

Because angle brackets can also represent (or occur in) the comparison operators <, >, <= and >=, macro expansion can't ignore commas inside angle brackets like it does within parentheses. (This is also a problem for square brackets and braces, even though those usually occur as balanced pairs.) You can enclose the macro argument in parentheses:

FOO((std::map<int, int>), map_var);

The problem is then that the parameter remains parenthesized inside the macro expansion, which prevents it being read as a type in most contexts.

A nice trick to workaround this is that in C++, you can extract a typename from a parenthesized type name using a function type:

template<typename T> struct argument_type;
template<typename T, typename U> struct argument_type<T(U)> { typedef U type; };
#define FOO(t,name) argument_type<void(t)>::type name
FOO((std::map<int, int>), map_var);

Because forming function types ignores extra parentheses, you can use this macro with or without parentheses where the type name doesn't include a comma:

FOO((int), int_var);
FOO(int, int_var2);

In C, of course, this isn't necessary because type names can't contain commas outside parentheses. So, for a cross-language macro you can write:

#ifdef __cplusplus__
template<typename T> struct argument_type;
template<typename T, typename U> struct argument_type<T(U)> { typedef U type; };
#define FOO(t,name) argument_type<void(t)>::type name
#else
#define FOO(t,name) t name
#endif
查看更多
何处买醉
7楼-- · 2019-01-02 18:26

If you can't use parentheses and you don't like Mike's SINGLE_ARG solution, just define a COMMA:

#define COMMA ,

FOO(std::map<int COMMA int>, map_var);

This also helps if you want to stringify some of the macro arguments, as in

#include <cstdio>
#include <map>
#include <typeinfo>

#define STRV(...) #__VA_ARGS__
#define COMMA ,
#define FOO(type, bar) bar(STRV(type) \
    " has typeid name \"%s\"", typeid(type).name())

int main()
{
    FOO(std::map<int COMMA int>, std::printf);
}

which prints std::map<int , int> has typeid name "St3mapIiiSt4lessIiESaISt4pairIKiiEEE".

查看更多
登录 后发表回答