X-macros:如何使变量列表在编译时可配置?
Posted
技术标签:
【中文标题】X-macros:如何使变量列表在编译时可配置?【英文标题】:X-macros: how to make the list of variables compile-time configurable? 【发布时间】:2017-05-16 15:16:30 【问题描述】:我有类似的代码
#define LIST_OF_VARIABLES \
X(value1) \
X(value2) \
X(value3)
如https://en.wikipedia.org/wiki/X_Macro中所述
现在我需要在编译时使 LIST_OF_VARIABLES 可配置
所以它实际上可以是例如
#define LIST_OF_VARIABLES \
X(default_value1) \
X(cust_value2) \
X(default_value3)
或例如
#define LIST_OF_VARIABLES \
X(default_value1) \
X(default_value2) \
X(cust_value3)
取决于之前定义的一些宏。 LIST_OF_VARIABLES 很长,自定义项相对较小。我不想复制每个定制的长列表,因为这会导致维护问题(DRY 原则https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)。事实上, LIST_OF_VARIABLES 应该在一个文件中,并且 其他地方的自定义(另一个文件或 Makefile 中的 -D 选项)
在伪代码中我在想类似的东西
#define X(arg) \
#ifdef CUST_##arg \
Y(CUST_##arg) \
#else \
Y(DEFAULT_##arg) \
#endif
然后使用名称 Y 下的 X 宏。
但这当然行不通,因为宏不能包含预处理器 指令。
有什么方法可以实现这一目标? C 是必须的(没有模板或 Boost 宏),gcc 特定的解决方案是可以接受的。
【问题讨论】:
Boost::Preprocessor 与 C 配合得很好。 @n.m.如果您有 Boost 解决方案,请随时发布。我认为我无法使用它,因为我的系统是嵌入式的(实际上是裸机),我无法轻松更改/增强工具链。但其他人可能会从中受益。 FWIW 我不相信 C 预处理器可以或不应该用来解决这个问题。但是如果要使用boost::preprocessor,在任何环境下应该都没有问题。这只是一堆标题。 如需快速帮助,请点击链接;您为 X 宏链接到的 wiki 文章有一个脚注。脚注链接到 Dobb 博士的日记。阅读它,您会发现另一种使用单独文件而不是列表宏的 X 宏方法。另一种方法是使用预处理器开关。我会在今天晚些时候(很多)有时间的时候将这些写在一个答案中,如果到那时没有其他人有的话。 请显示两个或多个版本的所需定制(-D
列表或单独文件的内容)以及所需预处理输出的相应版本。我不确定 Y 是什么。
【参考方案1】:
我认为你必须做的是:
#ifdef USE_DEFAULT_VALUE1
#define X_DEFAULT_VALUE1 X(default_value1)
#else
#define X_DEFAULT_VALUE1 /* omitted */
#endif
#ifdef USE_DEFAULT_VALUE2
#define X_DEFAULT_VALUE2 X(default_value2)
#else
#define X_DEFAULT_VALUE2 /* omitted */
#endif
#ifdef USE_DEFAULT_VALUE3
#define X_DEFAULT_VALUE3 X(default_value3)
#else
#define X_DEFAULT_VALUE3 /* omitted */
#endif
#ifdef USE_CUST_VALUE1
#define X_CUST_VALUE1 X(cust_value1)
#else
#define X_CUST_VALUE1 /* omitted */
#endif
#ifdef USE_CUST_VALUE2
#define X_CUST_VALUE2 X(cust_value2)
#else
#define X_CUST_VALUE2 /* omitted */
#endif
#define LIST_OF_VARIABLES \
X_DEFAULT_VALUE1 \
X_DEFAULT_VALUE2 \
X_DEFAULT_VALUE3 \
X_CUST_VALUE1 \
X_CUST_VALUE2 \
然后您需要根据您所追求的特定配置的需要定义USE_DEFAULT_VALUE1
等。
只要您始终需要相同顺序的项目,这就足够了。如果您需要它们以不同的顺序,那么您有条件地以不同的顺序定义LIST_OF_VARIABLES
。
【讨论】:
【参考方案2】:回答自己。
在 cmets 的帮助下,我想出了一个可行且符合大多数情况的解决方案 我提到的要求
加上“主代码”
$cat main.c
#ifndef VALUE1
#define VALUE1 value1
#endif
#ifndef VALUE2
#define VALUE2 value2
#endif
#ifndef VALUE3
#define VALUE3 value3
#endif
#define LIST_OF_VARIABLES \
X(VALUE1) \
X(VALUE2) \
X(VALUE3)
以及像
这样的自定义文件$cat cust1
-DVALUE2=value2cust
可以使用(GNUmake 伪语法)编译代码
$(CC) $(CFLAGS) $(shell cat cust1) main.c
实际上具有额外的间接性,每个值都定义在单个 行很好,因为它允许注释值。那不会有 可以通过单个 LIST_OF_VARIABLES 宏中的续行来实现。
编辑:不正确。扩展为空的 COMMENT(foo) 宏也可以解决该问题。 (信用:从@Jonathan Leffer 发布的答案中得到这个想法。)
但是该方法还没有满足我没有提到的以下要求
没有丑陋的样板代码(所有这些#ifndef 行都不是很好) 自定义还应该可以从 完全列出或添加全新的值(是的,这可能是 现在已经完成了一些丑陋的虚拟代码)所以对我自己的答案还不是很满意。需要考虑 多布斯博士文章中的方法多一点,也许可以使用。 打开以获得更好的答案。
【讨论】:
那里的另一个答案可以与您的答案相结合以满足您的第二个要点:除了您的VALUEn
宏之外,还使用USE_VALUEn
宏,您应该能够完全删除一些值。不过更多样板。【参考方案3】:
鉴于进一步的上下文,您似乎希望能够在编译时从列表中挑选单个值。我想你可能对预处理器开关感兴趣,它可以用更少的样板来完成你使用预处理器条件的事情。
通用预处理器开关
这是一个简短的框架:
#define GLUEI(A,B) A##B
#define GLUE(A,B) GLUEI(A,B)
#define SECONDI(A,B,...) B
#define SECOND(...) SECONDI(__VA_ARGS__,,)
#define SWITCH(NAME_, PATTERN_, DEFAULT_) SECOND(GLUE(NAME_,PATTERN_), DEFAULT_)
SWITCH 宏用法
调用SWITCH(MY_PREFIX_,SPECIFIC_IDENTIFIER,DEFAULT_VALUE)
将所有不是匹配模式的内容扩展为DEFAULT_VALUE
。 匹配模式的事物可以扩展为您将它们映射到的任何内容。
要创建匹配模式,请定义一个类似于宏的对象 MY_PREFIX_SPECIFIC_IDENTIFIER
,其替换列表由一个逗号组成,后跟您希望 SWITCH
在这种情况下扩展为的值。
这里的魔力很简单,SWITCH
构建了一个隐藏标记,给它一个扩展的机会(嗯,在这个实现中SECOND
的间接性也很重要),并为SECOND
注入了一个新的第二个参数如果它被定义。名义上,这个新令牌没有定义;在这种情况下,它只是成为SECOND
的第一个参数,它只是丢弃它,永远不会再被看到。
例如,给定上面的宏:
#define CONTRACT_IDENTIFIER_FOR_DEFAULT , overridden_id_for_default
#define CONTRACT_IDENTIFIER_FOR_SIGNED , overridden_id_for_signed
SWITCH(CONTRACT_IDENTIFIER_FOR_, DRAFT , draft )
SWITCH(CONTRACT_IDENTIFIER_FOR_, DRAWN , drawn )
SWITCH(CONTRACT_IDENTIFIER_FOR_, PROOFED , proofed )
SWITCH(CONTRACT_IDENTIFIER_FOR_, DELIVERED , delivered )
SWITCH(CONTRACT_IDENTIFIER_FOR_, SIGNED , signed )
SWITCH(CONTRACT_IDENTIFIER_FOR_, FULFILLED , fulfilled )
SWITCH(CONTRACT_IDENTIFIER_FOR_, DEFAULT , default )
...将扩展为:
draft
drawn
proofed
delivered
overridden_id_for_signed
fulfilled
overridden_id_for_default
装饰 X 宏
假设您希望为您的值命名,并简单地从命令行替换樱桃选择的值,您可以使用 SWITCH
来执行以下操作:
#define VARVALUE(N_,V_) SWITCH(VALUE_FOR_, N_, V_)
#define LIST_OF_VARIABLES \
X(VARVALUE(value1, default_value1)) \
X(VARVALUE(value2, default_value2)) \
X(VARVALUE(value3, default_value3))
VARVALUE 宏将首先应用在此表单中。要覆盖特定值,您可以使用 #define:
定义模式匹配器#define VALUE_FOR_value2 , custom_value2
...或在命令行/makefile 上:
CFLAGS += -DVALUE_FOR_value2=,custom_value2
使用 switch 宏禁用/插入
为了支持安全地禁用单个项目,嵌套两个开关并添加一个 EAT 宏来捕获条目:
#define EAT(...)
#define SELECT_ITEM_MACRO_FOR_STATE_ON , X
#define X_IF_ENABLED(N_, V_) \
SWITCH(SELECT_ITEM_MACRO_FOR_STATE_, SWITCH(ENABLE_VALUE_, N_, ON), EAT) \
(SWITCH(VALUE_FOR_, N_, V_))
#define LIST_OF_VARIABLES \
X_IF_ENABLED(value1, default_value1) \
X_IF_ENABLED(value2, default_value2) \
X_IF_ENABLED(value3, default_value3)
和以前一样,可以使用VALUE_FOR_valuex
模式宏覆盖单个宏,但这也允许使用ENABLE_VALUE_valuex
宏禁用项目,可以将其设置为除,ON
之外的任何内容以禁用该项目。
同样,添加对插入值的支持的一种方法是颠覆这个想法:
#define ADD_ITEM_MACRO_FOR_STATE_EAT , EAT
#define X_IF_ADDED(N_) \
SWITCH(ADD_ITEM_MACRO_FOR_STATE_, SWITCH(VALUE_FOR_, N_, EAT), X) \
(SECOND(GLUE(VALUE_FOR_,N_)))
#define LIST_OF_VARIABLES \
X_IF_ENABLED(value1, default_value1) \
X_IF_ENABLED(value2, default_value2) \
X_IF_ENABLED(value3, default_value3) \
X_IF_ADDED(value4) \
X_IF_ADDED(value5) \
X_IF_ADDED(value6)
...这允许您将VALUE_FOR_value4
定义为模式宏,但默认情况下将扩展为空。
总结
支持设置、删除或插入值的框架最终是:
#define GLUEI(A,B) A##B
#define GLUE(A,B) GLUEI(A,B)
#define SECONDI(A,B,...) B
#define SECOND(...) SECONDI(__VA_ARGS__,,)
#define SWITCH(NAME_, PATTERN_, DEFAULT_) SECOND(GLUE(NAME_,PATTERN_), DEFAULT_)
#define EAT(...)
#define SELECT_ITEM_MACRO_FOR_STATE_ON , X
#define X_IF_ENABLED(N_, V_) \
SWITCH(SELECT_ITEM_MACRO_FOR_STATE_, SWITCH(ENABLE_VALUE_, N_, ON), EAT) \
(SWITCH(VALUE_FOR_, N_, V_))
#define ADD_ITEM_MACRO_FOR_STATE_EAT , EAT
#define X_IF_ADDED(N_) \
SWITCH(ADD_ITEM_MACRO_FOR_STATE_, SWITCH(VALUE_FOR_, N_, EAT), X) \
(SECOND(GLUE(VALUE_FOR_,N_)))
在此框架下,您的列表宏将由一系列 X(value)
、X_IF_ENABLED(name,default_value)
和/或 X_IF_ADDED(name)
值组成,其中:
X(value)
可用于始终插入对具有值的 X 宏的调用
X_IF_ENABLED(name,default_value)
将使用 default_value 调用 X,允许您根据名称覆盖默认值。
X_IF_ADDED(name)
将提供一个带有名称的“空槽”,除非您覆盖该槽,否则它不会执行任何操作。
通过定义 VALUE_FOR_name
扩展为 ,replacement
来覆盖插槽。通过将ENABLE_VALUE_name
扩展为,OFF
来禁用启用的插槽。
Demo showing change, removal, addition using command line
【讨论】:
以上是关于X-macros:如何使变量列表在编译时可配置?的主要内容,如果未能解决你的问题,请参考以下文章
Kendo UI - 如何在编辑时使特定字段只读,同时在剑道网格中创建可编辑?