Comma in C/C++ macro
Solution 1
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
Solution 2
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"
.
Solution 3
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);
Solution 4
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.
Solution 5
This is possible with P99:
#include "p99/p99.h"
#define FOO(...) P99_ALLBUTLAST(__VA_ARGS__) P99_LAST(__VA_ARGS__)
FOO()
The code above effectively strips only the last comma in the argument list. Check with clang -E
(P99 requires a C99 compiler).
PoP
Updated on March 17, 2020Comments
-
PoP about 4 years
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 ?
-
Admin over 11 yearsOh, gosh... Why? Why not just enclose in parentheses?
-
Mike Seymour over 11 years@VladLazarenko: Because you can't always put arbitrary pieces of code in parentheses. In particular, you can't put parentheses around the type name in a declarator, which is exactly what this argument becomes.
-
James Kanze over 11 yearsFor the first solution, each macro will have to have a different name, since macros don't overload. And for the second, if you're passing in a type name, there's a very good chance that it will be used to declare a variable (or a typedef), so the parentheses will cause problems.
-
moliad over 10 years#define COMMA wow, you just saved me HOURS of work... why didn't I think of this years ago. Thanks for sharing this idea. This is even allowing me to build macros which setup functions with different argument counts altogether.
-
Will Custode almost 10 yearsThis is awesome. But how did you find out about this? I've been trying tons of tricks and never even thought that a function type would fix the issue.
-
ecatmur almost 10 years@WilliamCustode as I recall, I'd been studying the grammar of function types and function declarations with reference to the most vexing parse problem, so it was fortuitous that I was aware that redundant parentheses could be applied to a type in that context.
-
Roger Sanders over 9 yearsI found a problem with this method when working with templates. Let's say the code I wanted was this:
template<class KeyType, class ValueType> void SomeFunc(FOO(std::map<KeyType, ValueType>) element) {}
If I apply this solution here, the structs behind the macro become dependent types, and the typename prefix is now required on the type. You can add it, but type deduction has been broken, so you now have to manually list the type arguments to call the function. I ended up using temple's method of defining a macro for the comma. It might not look as pretty, but it worked perfectly. -
namezero about 9 yearsPlus 1 for the horror
-
not-a-user over 8 years@kiw If you
#define STRVX(...) STRV(__VA_ARGS__)
and#define STRV(...) # __VA_ARGS__
, thenstd::cout << STRV(type<A COMMA B>) << std::endl;
will printtype<A COMMA B>
andstd::cout << STRVX(type<A COMMA B>) << std::endl;
will printtype<A , B>
. (STRV
is for "variadic stringify", andSTRVX
is for "expanded variadic stringify".) -
kiw over 8 years@not-a-user yes, but with variadic macros you don't need the
COMMA
macro in the first place. That's what I ended up with. -
Rafael Baptista about 8 yearsI'd never use that, but +1 for being hilarious.
-
iFreilicht about 8 yearsNot sure why this is so far down, it's a much nicer solution than Mike Seymours. It's quick and simple and completely hidden from the user.
-
Cheers and hth. - Alf about 8 years@iFreilicht: It was posted a little over a year later. ;-)
-
VinGarcia over 7 yearsAnd because also it is hard to understand how and why it works
-
VinGarcia over 7 yearsA small problem on the answer: It states that commas are ignored inside
[]
and{}
, they are not, it only works with()
sadly. See: However, there is no requirement for square brackets or braces to balance... -
not-a-user over 7 years@kiw At least if you want to stringify only certain positional arguments of the macro, I think
COMMA
is useful. Please see my edit. -
BeeOnRope about 7 years... and also because you may only be able to modify the macro definition and not all the places that call it (which may not be under your control, or may be spread across 1000s of files, etc). This occurs, for example, when adding a macro to take over duties from a like-named function.
-
CygnusX1 over 4 years
(^...^)
this is one happy face :) -
user over 4 years@VinGarcia, you can explain why/how it works? Why the parentheses are required when calling it? What
UNPACK
do when used like this) UNPACK type name
? Whytype
correctly gets the type when used on) UNPACK type name
? Just what the hell is happening here? -
VinGarcia over 4 yearsNo @user, maybe Cheers and hth can answer you
-
user over 4 yearsI get it now. The paren on the function call makes the preprocessor not process the comma inside the paren. And the
UNPACK
macro removes the paren around thestd::map<int, int>
argument. This could be a definitive solution for the comma problem in macro arguments, however, what will happen when there are no paren around the macro argument? If I understand correctly, the generated code will be invalid because it will leave a danglingUNPACK
macro call hanging around. -
Trung0246 about 4 yearsNot sure but I think this solution isn't working on clang++ (9.0.0). But so far MSVC (Community 2019) worked.
-
not-a-user about 4 years@Trung0246 My example works, see wandbox.org/permlink/ITb5virVKfDMb0DO - please post your non-working code, so I can check.
-
Trung0246 about 4 years@not-a-user was trying to create my first library wandbox.org/permlink/Tji2igTxpbTz2x0R. Probably the code is too long since I have no idea why mine got errors.
-
Trung0246 about 4 yearsReplacing FUNCH_ARGS with COMMA will yield the same error also, while Visual Studio compiles everything just fine.
-
not-a-user about 4 years@Trung0246 You are probably an MS victim as per stackoverflow.com/questions/11469462/… - Anyway the
COMMA
macro will only survive one expansion. If the result is expanded again (as is does as you pass it down to other macros) it will be a a real comma and it will separate macro arguments. - Try to construct a simple (few-lines) example that triggers your problem. - At last, I think you are very much over-using macros in your code. I like to get an upvote for myCOMMA
from time to time, but I did not actually ever use it. -
Flamefire almost 4 yearsUnfortunately this does not work in MSVC: godbolt.org/z/WPjYW8. It seems MSVC doesn't allow adding multiple parens and fails to parse it. A solution which isn't as elegant but faster (less template instantiations) is to wrap the comma-ed argument into a wrapper macro:
#define PROTECT(...) argument_type<void(__VA_ARGS__)>::type
. Passing arguments is now easily possible even through multiple macros and for simple types you can ommit the PROTECT. However function types become function pointers when evaluated like this