Comma in C/C++ macro

46,531

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

Share:
46,531
PoP
Author by

PoP

Updated on March 17, 2020

Comments

  • PoP
    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
    Admin over 11 years
    Oh, gosh... Why? Why not just enclose in parentheses?
  • Mike Seymour
    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
    James Kanze over 11 years
    For 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
    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
    Will Custode almost 10 years
    This 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
    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
    Roger Sanders over 9 years
    I 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
    namezero about 9 years
    Plus 1 for the horror
  • not-a-user
    not-a-user over 8 years
    @kiw If you #define STRVX(...) STRV(__VA_ARGS__) and #define STRV(...) # __VA_ARGS__, then std::cout << STRV(type<A COMMA B>) << std::endl; will print type<A COMMA B> and std::cout << STRVX(type<A COMMA B>) << std::endl; will print type<A , B>. (STRV is for "variadic stringify", and STRVX is for "expanded variadic stringify".)
  • kiw
    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
    Rafael Baptista about 8 years
    I'd never use that, but +1 for being hilarious.
  • iFreilicht
    iFreilicht about 8 years
    Not 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
    Cheers and hth. - Alf about 8 years
    @iFreilicht: It was posted a little over a year later. ;-)
  • VinGarcia
    VinGarcia over 7 years
    And because also it is hard to understand how and why it works
  • VinGarcia
    VinGarcia over 7 years
    A 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
    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
    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
    CygnusX1 over 4 years
    (^...^) this is one happy face :)
  • user
    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? Why type correctly gets the type when used on ) UNPACK type name? Just what the hell is happening here?
  • VinGarcia
    VinGarcia over 4 years
    No @user, maybe Cheers and hth can answer you
  • user
    user over 4 years
    I 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 the std::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 dangling UNPACK macro call hanging around.
  • Trung0246
    Trung0246 about 4 years
    Not sure but I think this solution isn't working on clang++ (9.0.0). But so far MSVC (Community 2019) worked.
  • not-a-user
    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
    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
    Trung0246 about 4 years
    Replacing FUNCH_ARGS with COMMA will yield the same error also, while Visual Studio compiles everything just fine.
  • not-a-user
    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 my COMMA from time to time, but I did not actually ever use it.
  • Flamefire
    Flamefire almost 4 years
    Unfortunately 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