1
#include <vector>

struct Foo { int a, b, c; };

int main()
{
    Foo myFoo = Foo{ 1, 2, 3 };

    std::vector<Foo> listOfFoos;
    listOfFoos.push_back(Foo{ 1, 2, 3 });


#define push(x) listOfFoos.push_back(x)

    push(Foo{ 1, 2, 3 } ); // Error

}

Errors are:

> "Expected a '}'"  
> "Syntax error: expected a ')' not '}'"  "Syntax
> error: missing ')' before ';'"

It took me ages on Visual Studio to try to figure out what was happening. It wasn't until I compiled on an online compiler using GCC that I got a more descriptive error:

error: macro "push" passed 3 arguments, but takes just 1

I guess I'm confused because I thought std::initializer_list is one struct, and should get passed as one. When it complains that 3 arguments as being passed to the macro is it saying by doing push({1, 2, 3}); I'm doing the equivalent of push(1, 2, 3);? This would seem that std::initializer_list does a type of expansion of its elements before the precompiler stage when it resolves the macro. I dont understand why this is. Also, I tried wrapping it in another set of brackets and it works:

push( ( {1, 2, 3} ) );
Zebrafish
  • 11,682
  • 3
  • 43
  • 119
  • Your the structure declaration- `struct Foo { int a, b, c; };` is wrong. It should be `struct Foo { int a; int b; int c; };` – H.S. Oct 28 '17 at 02:42
  • @H.S. Are you sure? Declarations like this or int a = 1, b = 2, c = 3; are perfectly fine outside classes, unless there's a special rule against these in classes... – Zebrafish Oct 28 '17 at 02:46
  • Zebrafish : oops .. my bad, ignore my comment. – H.S. Oct 28 '17 at 02:53
  • The whole comma thing is confusing to me too. There's a whole Wikipedia article about the C comma operator, and I only half get it, apparently it evaluates all the comma-separated expressions, but just returns the last one, so in one example, someone asked what the following loop did which seems to have two conditions: for(i=0; j>=0, i<=5; i++), the only condition is the second one i <= 5, the first one in this case does nothing, as a condition I mean. – Zebrafish Oct 28 '17 at 03:02

1 Answers1

4

Macros are very primitive and limited, they do not (necessarily) know anything about the programming language they're used in.

Assume you have the macro

#define foo(x, y, z)

and use it like foo(1, 2, 3). The preprocessor splits at the comma (,) and sets the variables x, y, and z accordingly to the input numbers. In your macro-call, push(Foo{ 1, 2, 3 } ), this is not different. It splits at the comma and sets x to Foo{ 1. However, there are two more values, 2 and 3 }, hence the error. The curly braces are not special in any way for the preprocessor, its just another letter.

To just pass everything through, instead of taking one argument, take it as va-args:

#define push(...) listOfFoos.push_back(__VA_ARGS__)

where ... means, if there is anything extra, just take it and __VA_ARGS__ means expand to everything you got extra.

Hint: It's always nice to have a tab to godbolt.org with compiler flags set to -E open to check macro expansion. Example

tkausl
  • 13,686
  • 2
  • 33
  • 50
  • It's strange, because seeing as though I see the macros as simply paste jobs in the preprocessor stage, I expected {1, 2, 3} to go through untouched. – Zebrafish Oct 28 '17 at 02:31
  • @Zebrafish They are simple paste jobs, but not _that_ simple. They allow multiple parameters, so there has to be some way of splitting them. I'll post an workaround in a sec – tkausl Oct 28 '17 at 02:32
  • That's OK, unless you have a better way, as I mentioned if I wrap them in another set of brackets it works fine. It's not as if I intend to use this, I was just experimenting and trying to find the better way shorten the code, but I was surprised when this happened. – Zebrafish Oct 28 '17 at 02:35
  • @Zebrafish I've edited my answer, passing through va-args works. Or in a simple case like this, even `#define push listOfFoos.push_back` would do the job. – tkausl Oct 28 '17 at 02:37
  • Wow, I didn't know you could do that. That's nice. – Zebrafish Oct 28 '17 at 02:37
  • I'm guessing __VA_ARGS__ is a macro-specific term? – Zebrafish Oct 28 '17 at 02:39
  • I guess what I'm not understanding is why push(Foo{1, 2, 3}) is considered 3 arguments. And also by passing Foo{1, 2, 3} to push(...) listOfFoos.push_back(__VA_ARGS__); what does the it look like after macro expansion? Just as I typed it? Foo{1, 2, 3}? – Zebrafish Oct 28 '17 at 02:42
  • 1
    `push(Foo{1, 2, 3})` is considered 3 arguments because the preprocessor primitively splits at the `,`, ignoring the curly braces. You can check macro-espansion by compiling with the `-E` flag, for example: https://godbolt.org/g/SzgpPF (the output window in the middle shows the result) – tkausl Oct 28 '17 at 02:45
  • 1
    I see, so doing it as just #define push listOfFoos.pushback instead of #define push(x) listOfFoos.pushback(x) works. OK, thanks. – Zebrafish Oct 28 '17 at 02:49
  • You could also use a lambda. `auto push = [&](auto&& x) { listOfFoos.push_back(x); };` – Benjamin Lindley Oct 28 '17 at 03:42
  • @BenjaminLindley I might be wrong, but I think you need an `std::forward` there to perfectly forward ([Perfect forwarding in a lambda](https://stackoverflow.com/questions/42799208/perfect-forwarding-in-a-lambda)) – tkausl Oct 28 '17 at 03:48
  • `push((Foo{1,2,3}))` would also have worked, but the argument is then `(Foo{1,2,3})`, which is OK in this particular case. That works because the preprocessor does not split on commas *inside parentheses*. (Only parentheses. Neither brackets nor braces protect commas.) – rici Oct 29 '17 at 18:34