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:如何使变量列表在编译时可配置?的主要内容,如果未能解决你的问题,请参考以下文章

如何使每个单元格在 jquery 数据表中单击按钮时可编辑

Kendo UI - 如何在编辑时使特定字段只读,同时在剑道网格中创建可编辑?

如何配置myeclipse编译环境

如何使属性比较能够编译为 SQLAlchemy 中的 SQL 表达式?

使 JSON 文件的属性在需要时可单击

如何配置 LLVM 以编译没有 int64 的 wasm 目标?