Is it legal to pass the macro name to an X-Macro list
Asked Answered
T

1

5

It occurred to me that the following would be a preferable style of X-macro trick:

#define LIST_OF_COLOURS(X) \
    X(RED) \
    X(GREEN) \
    X(BLUE)

#define LIST_OF_FRUIT(X) \
    X(APPLE) \
    X(ORANGE) \
    X(TOMATO)

Specifically, passing the X macro to the list, rather than undefining and redefining it every time the list is instantiated. This allows:

#define X_LIST(x) x,
#define X_STRING_LIST(x) #x,
#define COMPREHENSIVE_SETUP(n, l)  \
    enum n { l(X_LIST) };  \
    char const* n##Names[] = { l(X_STRING_LIST) };

COMPREHENSIVE_SETUP(Colour, LIST_OF_COLOURS)
COMPREHENSIVE_SETUP(Fruit, LIST_OF_FRUIT)

But the problem is that I don't regularly see that idiom in the wild, and it's not what Wikipedia describes, even though it "seems to work" whenever I try it and feels much more convenient.

My question is, is this actually legal and fully defined, or am I relying on undefined behaviour?

Tanka answered 18/7, 2019 at 5:39 Comment(1)
Just came across this in the wild (libuv). Kinda blew my mind. I was only familiar with the x macros described in wikipedia. github.com/libuv/libuv/blob/v1.x/include/uv.h#L1790-L1794Belittle
S
6

Yes, it'd valid. Pre-processing of function like macros is described in the C standard by §6.10.3 Macro replacement. The pertinent parts are the following:

¶10 ...Each subsequent instance of the function-like macro name followed by a ( as the next preprocessing token introduces the sequence of preprocessing tokens that is replaced by the replacement list in the definition (an invocation of the macro)....

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.

6.10.3.4 Rescanning and further replacement

¶1 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.

Except for section names and numbering, the same wording exists in the C++ standard too.

So when you plug X_LIST in, the preprcoessor will replace X by it after attempting to expand X_LIST as though it was an object like macro. Since it's not, the tokens left with for X is X_LIST.

Then the preprocessor scans the line again. This time X_LIST will be followed by a (, and so will be expanded now.

Passing a function like macro's name to a "higher order function" is not unheard of. The Boost.Preprocessor library makes heavy use of this idiom.

Sedimentary answered 18/7, 2019 at 5:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.