Does recursion in the C preprocessor abuse an inconsistency in the standard?

Viewed 161

Consider this code:

#define MAP_OUT

#define A(x) B MAP_OUT (x)
#define B(x) A MAP_OUT (x)

A(x)

Then A(x) expands to B MAP_OUT (x), then B (x). Now take a look at the standard:

After all parameters in the replacement list have been substituted and # and ## processing has taken place, all placemarker preprocessing tokens are removed. The resulting preprocessing token sequence is then rescanned, along with all subsequent preprocessing tokens of the source file, for more macro names to replace.

Does B (x) belong to "resulting preprocessing token sequence for more macro names to replace"? All compilers I have tried don't expand B (x) during a single scan, but what about the standard itself?

2 Answers

Does B (x) belong to "resulting preprocessing token sequence for more macro names to replace"?

No, absolutely not. Read again:

After all parameters in the replacement list have been substituted and # and ## processing has taken place, all placemarker preprocessing tokens are removed. The resulting preprocessing token sequence is then rescanned.

The preprocessing token sequence that results from parameter replacement in A(x) is precisely B MAP_OUT (x), nothing more, nothing less. This sequence is then scanned for more macros to replace, once. There is only one eligible macro to replace in there, MAP_OUT. Then the replacement of MAP_OUTis scanned, nothing is found, and the processing is resumed.

There is no indication whatsoever that B in B MAP_OUT (x) should be scanned twice.

You're cherry-picking. The standard requires that rescanning and replacement stops.

There are other paragraphs, with identical wording in every C++ standard since C++98 (not just the one you've quoted) that actually control the behaviour you observe.

After all parameters in the replacement list have been substituted, the resulting preprocessing token sequence is rescanned with all subsequent preprocessing tokens of the source file for more macro names to replace.

If the name of the macro being replaced is found during this scan of the replacement list (not including the rest of the source file’s preprocessing tokens), it is not replaced. Further, if any nested replacements encounter the name of the macro being replaced, it is not replaced. These nonreplaced macro name preprocessing tokens are no longer available for further replacement even if they are later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced.

As I said, the wording is identical in every C++ standard. Only the section and para numbers change.

  • In C++98, the above quote is Section 16.3.4 "Rescanning and further replacement", paras 1 and 2;
  • In C++17 the above quote is Section 19.3.4 "Rescanning and further replacement", paras 1 and 2;
  • In the latest C++20 draft (at least, the latest I've accessed) the above quote is Section 15.6.4 "Rescanning and further replacement", paras 1 and 3 (there is an added para 2 with an illustrative example, not normative text).
Related