5

我突然想到,以下是X 宏技巧的一种更可取的风格:

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

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

具体来说,将X宏传递给列表,而不是在每次实例化列表时取消定义和重新定义它。这允许:

#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)

但问题是我不经常在野外看到这个成语,而且它不是维基百科所描述的,尽管每当我尝试它并且感觉更方便时它“似乎工作”。

我的问题是,这实际上是合法的和完全定义的,还是我依赖​​未定义的行为?

4

1 回答 1

6

是的,它是有效的。C 标准中的第6.10.3 节宏替换描述了类似宏的函数的预处理。相关部分如下:

¶10 ...类函数宏名称的每个后续实例后跟(作为下一个预处理标记的 a 引入了预处理标记的序列,该序列由定义中的替换列表替换(宏的调用)....

6.10.3.1 参数替换

¶1在识别出调用类函数宏的参数后,将进行参数替换。替换列表中的参数,除非前面有 # 或 ## 预处理标记或后跟 ## 预处理标记(见下文),在其中包含的所有宏都已展开后,将替换为相应的参数。在被替换之前,每个参数的预处理标记都被完全宏替换,就好像它们形成了预处理文件的其余部分一样;没有其他可用的预处理令牌。

6.10.3.4 重新扫描和进一步更换

¶1在替换列表中的所有参数都被替换并且#和##处理已经发生之后,所有placemarker预处理标记都被删除。然后重新扫描生成的预处理标记序列以及源文件的所有后续预处理标记,以替换更多宏名称。

除了部分名称和编号之外,C++ 标准中也存在相同的措辞。

因此,当您插入时,预处理器将在尝试扩展后X_LIST替换为它,就好像它是宏之类的对象一样。既然不是,那么 for 留下的标记就是。XX_LISTXX_LIST

然后预处理器再次扫描该行。这一次X_LIST后面会跟着a ,所以现在(会展开。

将宏名称之类的函数传递给“高阶函数”并非闻所未闻。Boost.Preprocessor库大量使用了这个习语。

于 2019-07-18T05:54:16.733 回答