128

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 ?

PoP
  • 2,055
  • 3
  • 16
  • 20

8 Answers8

151

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

not-a-user
  • 4,088
  • 3
  • 21
  • 37
138

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
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 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. – Will Custode Jul 12 '14 at 12:34
  • @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. – ecatmur Jul 14 '14 at 09:33
  • I found a problem with this method when working with templates. Let's say the code I wanted was this: `template void SomeFunc(FOO(std::map) 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. – Roger Sanders Oct 01 '14 at 04:30
  • 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...](https://gcc.gnu.org/onlinedocs/cpp/Macro-Arguments.html) – VinGarcia Sep 23 '16 at 14:49
  • Unfortunately this does not work in MSVC: https://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::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 – Flamefire Jul 10 '20 at 17:35
79

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);
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • 1
    Oh, gosh... Why? Why not just enclose in parentheses? –  Dec 12 '12 at 15:09
  • 19
    @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. – Mike Seymour Dec 12 '12 at 15:11
  • 3
    ... 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. – BeeOnRope Feb 12 '17 at 18:45
42

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.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • 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. – iFreilicht Apr 16 '16 at 00:07
  • 3
    @iFreilicht: It was posted a little over a year later. ;-) – Cheers and hth. - Alf Apr 16 '16 at 00:19
  • 7
    And because also it is hard to understand how and why it works – VinGarcia Sep 23 '16 at 14:16
  • 1
    @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? – Evandro Coan Dec 14 '19 at 23:52
  • No @user, maybe Cheers and hth can answer you – VinGarcia Dec 16 '19 at 22:29
  • 1
    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` 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. – Evandro Coan Dec 16 '19 at 23:07
4

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

xiaq
  • 764
  • 1
  • 6
  • 13
3

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.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • 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. – James Kanze Dec 12 '12 at 15:13
3

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

James Kanze
  • 150,581
  • 18
  • 184
  • 329
3

You can actually include arguments containing commas without much fuss as long it's the last argument, by using __VA_ARGS__ in an amazingly hacky way:

#define EXAMPLE(_arg1_, _arg2_, ...) \
for (int i = _arg1_; i < _arg2_; i++){ __VA_ARGS__ } 

since in the context of macros, __VA_ARGS__ simply represents the variadic arguments separated by commas, as in:

EXAMPLE(5, 3, int[2] = {i,i})

//macro sees:   5,   3,   int[2] = {i,   i}

because __VA_ARGS__ reinserts commas between the arguments, the macro will pass your code through just as intended.

I'm supposed to tell you not to do this, but you totally should because abusing macros is the best part of c++.