Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 411
sh1 Avatar asked Jul 18 '19 05:07

sh1


People also ask

What Cannot be included in a macro name?

Valid characters in identifier names include digits and non-digits and must not start with a digit. non-digits include the uppercase letters A-Z, the lowercase letters a-z, the underscore, and any implementation defined characters.

Can you define a macro with another macro?

Short answer yes. You can nest defines and macros like that - as many levels as you want as long as it isn't recursive.

How do you pass a macro to a function?

The more things you check, the more robust the macro is, and the slower it runs. Passing one argument is as easy as passing two: add another argument to the function definition (see Listing 6). When calling a function with two arguments, separate the arguments with a semicolon; for example, =TestMax(3; -4).


1 Answers

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.

like image 168
StoryTeller - Unslander Monica Avatar answered Oct 02 '22 14:10

StoryTeller - Unslander Monica