1

I just noticed an interesting thing about the expansion of the macro parameters in C++.

I defined 4 macros; 2 of them turn given parameter into string and another 2 try to separate 2 arguments. I passed them argument with macro which expands into , and got the following results:

#define Quote(x) #x
#define String(x) Quote(x)
#define SeparateImpl(first, second) first + second
#define Separate(pair) SeparateImpl(pair)
#define comma ,

int main(){
Quote(1 comma 2);  // -> "1 comma 2"
String(1 comma 2); // -> "1 , 2"
SeparateImpl(1 comma 2); // -> 1 , 2 + *empty arg*
Separate(1 comma 2);     // -> 1 , 2 + *empty arg*
return 0;
}

So, as we see macro String turned into "1 , 2", that means macro comma had been unpacked first. However, macro Separate turned into 1 , 2 + **empty arg**, that means macro comma hadn't been unpacked first and I wonder why? I tried this in VS2019.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • 4
    Fortunately, nobody actually has to write real C or C++ code like that, so this is not very important anyway. – Sam Varshavchik Aug 13 '19 at 10:59
  • AFAIK, macros are processed from top to down but this might be repeated. Hence, macros can expand to other macros which then expand to something else. (["Blue painting"](https://en.wikipedia.org/wiki/Painted_blue) might be relevant in this context.) I did [google "c preprocessor blue painting"](https://www.google.com/search?q=c+preprocessor+blue+painting) and found some interesting links (e.g. [Is the C preprocessor Turing complete?](http://pfultz2.com/blog/2012/05/10/turing/)) although I failed to find (or see) the ultimate explanation I hoped for. – Scheff's Cat Aug 13 '19 at 11:06
  • 1
    ... However, beside of this I want to emphasize the recommendation of @SamVarshavchik: Macros in C++ are dangerous. While the might add nicely to C, in C++ they can introduce terrible effects as they are fully namespace agnostic and might change code where the author doesn't expect this. (It already happend to me and it caused me some head-aches until I understood this...) – Scheff's Cat Aug 13 '19 at 11:09
  • Don't know what the (a) standard says, but CLANG behaves differently. `String(1 comma 2);` gives "_too **many** arguments_" (as it invokes `Quote()`) and `SeparateImpl(1 comma 2);` gives "_too **few** arguments_". The call `Separate(1 comma 2);` _does_ expand to a two-argument invocation of `SeparateImpl()`. Looks like VS2019 always treats the single argument as a single argument (even though it contains a comma). – TripeHound Aug 13 '19 at 12:47

1 Answers1

1
#define Quote(x) #x
#define String(x) Quote(x)
#define SeparateImpl(first, second) first + second
#define Separate(pair) SeparateImpl(pair)
#define comma ,

Macro invocation proceeds as follows:

  • Argument substitution (a.s), where if a parameter is mentioned in the replacement list and said parameter does not participate in a paste or stringification, it is fully expanded and said mentions of the parameter in the replacement list are substituted with the result.
  • Stringification
  • Pastes
  • Rescan and further replacement (r.a.f.r.), where the resulting replacement list is rescanned, during which the macro's name is marked as invalid for expansion ("painted blue").

Here's how each case should expand:

Quote(1 comma 2)

a.s. no action (only mention of parameter is stringification). Stringification applies. Result: "1 comma 2".

String(1 comma 2)

a.s. applies; yielding Quote(1 , 2). During r.a.f.r., Quote identified as a macro, but the argument count doesn't match. This is invalid. But see below.

SeparateImpl(1 comma 2)

Invalid macro call. The macro is being invoked with one argument, but it should have 2. Note that comma being defined as a macro is irrelevant; at the level of macro invocation you're just looking at the tokens.

Separate(1 comma 2)

a.s. applies; yielding SeparateImpl(1 , 2). During r.a.f.r., SeparateImpl is invoked... that invocation's a.s. applies, yielding 1 + 2.

I tried this in VS2019.

I could tell from a glance it was VS something before 2020, where the walls tells me they're finally going to work on preprocessor compliance. VS in particular seems to have this strange state in which tokens with commas in them nevertheless are treated as single arguments (it's as if argument identification occurs before expansion but continues to apply or something); so in this case, 1 , 2 would be that strange thing in your String(1 comma 2) call; i.e., Quote is being called with 1 , 2 but in that case it's actually one argument.

H Walters
  • 2,634
  • 1
  • 11
  • 13