X-macros:如何使变量列表编译时可配置?

时间:2017-05-16 15:16:31

标签: c macros c-preprocessor

我的代码类似于

#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-macro。

但当然这不起作用,因为宏不能包含预处理器 指令。

实现这一目标的方法是什么? C是必须的(没有模板或Boost 宏),gcc的具体解决方案是可以接受的。

3 个答案:

答案 0 :(得分: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

答案 1 :(得分:1)

回答自己。

在评论的帮助下,我提出了一个有效且最符合要求的解决方案 我曾提到的要求

使用"主要代码"

$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

实际上对单个上定义的每个值都有额外的间接性 line很好,因为它允许注释值。那不可能 可以使用单个LIST_OF_VARIABLES宏中的连续行。

修改:不正确。一个扩展到零的COMMENT(foo)宏也会解决这个问题。 (信用:从@Jonathan Leffer发布的答案中得到了想法。)

然而,该方法尚未满足我未提及的以下要求

  • 没有丑陋的样板代码(所有这些#ifndef行都不是很好)
  • 自定义还应该可以从中删除默认值 完全列出或添加全新的值(是的,这可能是 现在已经完成了一些丑陋的虚拟代码)

因此我对自己的答案并不满意。需要考虑一下 来自Dr. Dobbs文章的方法多一点,也许可以使用。 打开以获得更好的答案

答案 2 :(得分:0)

如果有更多上下文,您似乎希望能够在编译时从列表中 cherry选择个别值。我想你可能对预处理器开关很感兴趣,它可以用很少的样板来完成你使用预处理器条件的工作。

通用预处理器开关

这是一个简短的框架:

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

假设您希望提供值名称,并简单地从命令行替换cherry pick值,您可以使用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

使用开关宏

禁用/插入

要支持安全地禁用单个项目,请嵌套两个开关并添加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