C 宏将单个标记拆分为两个

C macro to split a single token into two

我如何制作一个 C 宏,它接受由 space 分隔的单词列表并将它们拆分?

我想要一个宏,例如下面的 DECLARE()。

#define EXPAND(xy) /* expand sequence of words separated by space into 2 */

#define DECLARE(xy) DECLARE_2(EXPAND(xy)) /* x and y are separated by a space, each should go to each argument of DECLARE_2 */

#define DECLARE_2(const, type) char *type##_str = #type; const type

这样我可以做:

typedef struct MyStruct { int value; } MyStruct;
DECLARE(const MyStruct) x = { 2 };
print(MyStruct_str); // prints 'MyStruct'

C 中的宏在 pre-processor 中被替换,因此如果您要编写这样做的函数,您可以将宏用于“糖语法”,但宏本身不提供任何功能。 间距问题可以参考这个回答:

How can I make a C macro that takes a list of words separated by a space and split them up?

你不能,至少不能以任何一般方式仅使用标准 C 定义的预处理设施。function-like 预处理器宏的各个参数可以是多个(预处理)标记的序列,如你所描述的,但是宏只能用它们的参数做这些事情:

  • 将它们完整地插入到其他标记序列中(包括通过将它们作为参数间接传递给其他宏)
  • 将它们转换为字符串
  • 将第一个 and/or 最后一个与其他标记连接起来

您可以使用最后一项结合宏扩展的自动重新扫描来做一些有趣的事情,但它们无法以任何一般方式让您到达您想去的地方。

使用宏,您可以使其在非常特定的情况下工作:您有一个 y(最后一个标记)的预期值列表。

在这个例子中,我们预期的最​​后一个标记列表将是 hellolovelyworld,但您可以根据您的目的简单地更改和扩展它们。

#define hello_SEPARATE ,hello
#define lovely_SEPARATE ,lovely
#define world_SEPARATE ,world

#define SEPARATE(xy) xy##_SEPARATE

这些是一些示例输出:

SEPARATE(hello world)        // hello ,world
SEPARATE(world hello)        // world ,hello
SEPARATE(hello lovely)       // hello ,lovely
SEPARATE(goodbye lovely)     // goodbye ,lovely
SEPARATE(I love you world)   // I love you ,world

SEPARATE(hello)              // ,hello
SEPARATE(goodbye)            // goodbye_SEPARATE
SEPARATE(lovely goodbye)     // lovely goodbye_SEPARATE

如最后一个示例所示,如果最后一个标记是意外的,它将无法按预期工作。


我们可以通过处理失败时发生的不稳定行为来进一步改进上述解决方案。

您可能已经注意到在最后的示例中奇怪地在开头添加了一个逗号(即使只有一个关键字存在)和结尾的“_SEPARATE”。这是粗略定义宏的结果。

要修复它,我们可以利用以下事实:

  1. C 预处理器将仅在其后跟括号时触发类函数宏。
  2. 我们可以检测可变参数宏参数中的逗号 (,),方法是移动可变参数宏中的参数(类似于此 answer on counting VA_ARGS, and explained in this blog post)。
  3. 我们可以使用事实 2 和标记连接 (##) 来制作伪 if 语句。
  4. 我们可以使用 1、2 和 3 来检测可变参数宏是否没有参数(正如 Jens Gustedt 在 blog post 中所解释的)。

这是最终的解决方案:

// Boilerplate
#define EXPAND(x) x

#define _GLUE(X,Y) X##Y
#define GLUE(X,Y) _GLUE(X,Y)

#define _ARG_100(_,\
   _100,_99,_98,_97,_96,_95,_94,_93,_92,_91,_90,_89,_88,_87,_86,_85,_84,_83,_82,_81, \
   _80,_79,_78,_77,_76,_75,_74,_73,_72,_71,_70,_69,_68,_67,_66,_65,_64,_63,_62,_61, \
   _60,_59,_58,_57,_56,_55,_54,_53,_52,_51,_50,_49,_48,_47,_46,_45,_44,_43,_42,_41, \
   _40,_39,_38,_37,_36,_35,_34,_33,_32,_31,_30,_29,_28,_27,_26,_25,_24,_23,_22,_21, \
   _20,_19,_18,_17,_16,_15,_14,_13,_12,_11,_10,_9,_8,_7,_6,_5,_4,_3,_2,X_,...) X_
#define HAS_COMMA(...) EXPAND(_ARG_100(__VA_ARGS__, \
   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \
   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ,1, \
   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0))

#define _TRIGGER_PARENTHESIS_(...) ,
#define _PASTE5(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4
#define _IS_EMPTY_CASE_0001 ,
#define _IS_EMPTY(_0, _1, _2, _3) HAS_COMMA(_PASTE5(_IS_EMPTY_CASE_, _0, _1, _2, _3))
#define IS_EMPTY(...)  \
   _IS_EMPTY(                                                               \
      HAS_COMMA(__VA_ARGS__),                                       \
      HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__),                 \
      HAS_COMMA(__VA_ARGS__ (/*empty*/)),                           \
      HAS_COMMA(_TRIGGER_PARENTHESIS_ __VA_ARGS__ (/*empty*/))      \
   )

// Place where you define your expected last tokens
#define hello_REMOVE
#define lovely_REMOVE
#define world_REMOVE

#define hello_SEPARATE ,hello
#define lovely_SEPARATE ,lovely
#define world_SEPARATE ,world

// SEPARATE Macro
#define _IS_EMPTY_TRIGGERED_0(xy) xy##_SEPARATE
#define _IS_EMPTY_TRIGGERED_1(xy) xy

#define _KEYWORD_TRIGGERED_0(xy) xy
#define _KEYWORD_TRIGGERED_1(xy) GLUE(_IS_EMPTY_TRIGGERED_, IS_EMPTY(xy##_REMOVE))(xy)

#define SEPARATE(xy) GLUE(_KEYWORD_TRIGGERED_, HAS_COMMA(xy##_SEPARATE()))(xy)

以及示例列表:

SEPARATE(hello world)        // hello ,world
SEPARATE(world hello)        // world ,hello
SEPARATE(hello lovely)       // hello ,lovely
SEPARATE(goodbye lovely)     // goodbye ,lovely
SEPARATE(I love you world)   // I love you ,world

SEPARATE(hello)              // hello
SEPARATE(goodbye)            // goodbye
SEPARATE(lovely goodbye)     // lovely goodbye

正如您所注意到的,宏在失败时的行为要好得多:它只是 returns 返回您输入的内容。

此行为也是可自定义的。更改“_IS_EMPTY_TRIGGERED_1”(仅检测到一个关键字案例)and/or“_KEYWORD_TRIGGERED_0”(未检测到关键字案例)的定义将更改失败情况下的行为。例如,如果您想输出一些错误消息,您可以将这些行替换为:

//...
#define _IS_EMPTY_TRIGGERED_1(xy) ERROR_1

#define _KEYWORD_TRIGGERED_0(xy) ERROR_2
//...

这将是结果:

SEPARATE(hello world)        // hello ,world
SEPARATE(world hello)        // world ,hello
SEPARATE(hello lovely)       // hello ,lovely
SEPARATE(goodbye lovely)     // goodbye ,lovely
SEPARATE(I love you world)   // I love you ,world

SEPARATE(hello)              // ERROR_1
SEPARATE(goodbye)            // ERROR_2
SEPARATE(lovely goodbye)     // ERROR_2

注意:在最终版本中HAS_COMMA之后的EXPAND是额外的扩展步骤,以补偿MSVC does not expand __VA_ARGS__ like most other compilers.[=71这一事实=]