2

This is a follow-up question to this one (and also closer to the actual problem at hand).

If I have the following case:

#include <stdio.h>

#define FOO_ONE 12
#define FOO_TWO 34
#define BAR_ONE 56
#define BAR_TWO 78

#define FOO 99

#define STRINGIFY(mac) #mac
#define CONCAT(mac1, mac2) STRINGIFY(mac1) STRINGIFY(mac2)
#define MAKE_MAC(mac) CONCAT(mac##_ONE, mac##_TWO)

#define PRINT(mac) printf(#mac ": " MAKE_MAC(mac) "\n")

void main(int argc, char *argv[])
{
    PRINT(FOO);
    PRINT(BAR);
}

As can be seen, the stringified, concatenated macros are then substituted inside a printf() statement which itself is inside a macro.

Because FOO is defined (as 99), it happens that it is expanded before the concatenation with _ONE and _TWO, effectively creating the tokens 99_ONE and 99_TWO.

This program outputs:

FOO: 99_ONE99_TWO
BAR: 5678

How can I defer the expansion of the FOO macro (effectively, eliminating it altogether, to get the required output of:

FOO: 1234
BAR: 5678

NOTE: assume the PRINT() macro signature cannot be changed (i.e., can't add a parameter, etc.). However its implementation can be changed. Also, FOO, FOO_* and BAR_* definitions cannot be modified as well.

ysap
  • 7,723
  • 7
  • 59
  • 122
  • As none of `FOO`, `FOO_*` and `BAR_*` can be modified, how to prevent the substitution from `FOO` to `99` that happens in `PRINT(FOO)`? – Stan May 30 '18 at 03:40
  • @Stan - indeed a problem... which is why I am looking for SE gurus for a creative idea... 8-) – ysap May 30 '18 at 04:03
  • @Stan - I was hoping maybe a reordering of the underlying macro cascade will do the trick, but you emphasized the point that the root of the problem is the API macro itself. – ysap May 30 '18 at 04:11
  • Indeed, it'd be much more elegant to abandon the macro idea in this case. What's the original need for such an implementation? Can't `FOO`, `BAR` be arrays? As you mentioned in another comment there're hundreds of macros, to increase the flexibility, we may also consider encapsulating all the macros into "actual" variables and functions. – Stan May 30 '18 at 04:19

3 Answers3

3

If you can do something each time FOO is used, you could first undefine the macro inorder for it expand as you want and then redefine it as in

#undef FOO
PRINT(FOO);
#define FOO 99

in which case it will expand to

printf("FOO" ": " "12" "34" "\n");

printf("BAR" ": " "56" "78" "\n");

to print what you want.

J...S
  • 5,079
  • 1
  • 20
  • 35
  • This may be the start of a solution. However, how'd you detect when `FOO` is used, rather than `BAR`? In practice, `FOO` and `BAR` are a list of 100's of macros, which we receive from an external source. A minority of them have the problem expressed in this question. – ysap May 30 '18 at 04:06
  • Maybe if, in the definition of `PRINT()` you could save the `mac` value into a temporary macro, then undef `mac`, call `PRINT()` and then redefine with the temp macro as its new value? – ysap May 30 '18 at 04:08
  • 1
    @ysap I was trying but could not find a solution if the macros is used hundreds of times. – J...S May 30 '18 at 04:29
  • 2
    @ysap Macros aren't variables. You assign a macro with a `#define`, and you can't `#define` from a replacement list. The closest you can do to semantics like "saving off" are value-only things using complex techniques, such as those done by boost pp slots, which involve `#include` (another thing you can't do inside a macro). – H Walters May 30 '18 at 04:43
  • 1
    @HWalters - you are correct, of course, which is why I post such questions in SE. Many times you get some novel ideas of how to exploit C features. – ysap May 30 '18 at 14:18
2
How can I defer the expansion of the FOO macro ... NOTE: assume the PRINT() macro signature cannot be changed (i.e., can't add a parameter, etc.)

You can't.

Macro expansions go through a series of steps:

  • Argument substitution
  • Paste and stringification in no particular order
  • Rescan and further replacement

Argument substitution occurs with your arguments; in the case of the invocation PRINT(FOO), FOO; and it's the very first step. By the time you even get the preprocessor to recognize that something in your replacement list is a macro, you've long past argument substitution.

The rule for argument substitution is that if any of your parameters are mentioned in your replacement list, and those parameters are being neither stringified nor pasted, then the corresponding argument is fully evaluated and those mentions of the parameters are replaced with the result. In this case, PRINT(FOO), after argument substitution, results in the replacement list:

printf(#mac ": " MAKE_MAC(99) "\n")

Again, MAKE_MAC's definition doesn't matter; it's not even recognized as a macro until rescan and further replacement.

Now, without your restriction, you could defer FOO from expanding... by adding a second parameter to PRINT and pasting to it (that disqualifies it from a.s., and by the time rescan and replacement comes along it'd be invoking your next macro). But with your restriction, you're DOA.

H Walters
  • 2,634
  • 1
  • 11
  • 13
  • Thanks for the elaborate explanation. Basically, what @Stan said in the comment to the question. – ysap May 30 '18 at 14:14
1

The actual expansion of FOO to 99 happens at the moment when PRINT is expanded and before its body is re-scanned one more time. The relevant part of the ANSI standard is:

6.10.3.1 Argument substitution

1 After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. A parameter in the replacement list, unless preceded by a # or ## preprocessing token or followed by a ## preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.

In your case you can avoid expansion of FOO (inside mac) by replacing

#define PRINT(mac) printf(#mac ": " MAKE_MAC(mac) "\n")

with

#define PRINT(mac) printf(#mac ": " CONCAT(mac##_ONE, mac##_TWO) "\n")
Community
  • 1
  • 1
Marian
  • 7,402
  • 2
  • 22
  • 34
  • Thanks for the elaborate explanation. Basically, what @Stan said in the comment to the question. I am selecting this answer over the other one b/c you provided the quote from the standard. – ysap May 30 '18 at 14:16
  • @ysap Not sure to understand your comment. I've answered the original question: how to define `PRINT` to get output `FOO: 1234 BAR: 5678`. – Marian May 30 '18 at 14:33
  • Marian - of course, I failed to read the 2nd part of your answer. Coincidentally, I was thinking along the same lines just before you posted your comment, but you helped nail the solution. I modified the program and now it builds just right! – ysap May 30 '18 at 15:23