C 预处理器宏不解析逗号分隔的标记?
C preprocessor macro doesn't parse comma separated tokens?
我想根据参数的数量选择两个函数之一:
nargs = 0
----> f1
nargs > 0
----> f2.
宏执行以下操作:获取第一个参数,然后如果没有提供参数,它将添加两个逗号“,NULL,NULL
”。然后它将 select 返回的参数列表中的第二个参数。
例如:
f("Hello, world%i%s", x , s)
----> "Hello, world%i%s
" ----> void
f()
----> ,NULL,NULL
----> NULL
所以我可以根据参数的数量得到 null 或 void。
这是宏:
#define FIRST(a, ...) a
#define COMMA_IF_PARENS(...) ,NULL,NULL
#define IS_EMPTY(...) COMMA_IF_PARENS __VA_ARGS__ ()
#define RM_FIRST(x, ...) __VA_ARGS__
#define CHOOSE(...) IS_EMPTY(FIRST(__VA_ARGS__))
#define ff_(...)() CHOOSE (__VA_ARGS__)
#define FF(...)() ff_(__VA_ARGS__) ()
#define FX(...) RM_FIRST (FF(__VA_ARGS__) ())
FF 宏的输出:
FF()
---->,((void*)0),((void*)0);
FF("Hello, world%i%s")
----> COMMA_IF_PARENS "Hello, world%i%s" ();
FX 宏的输出:
FX()
---> void
FX("Hello, world%i%s")
----> void
预期外汇输出:
FX()
----> NULL
FX("Hello, world%i%s")
----> void
问题是从 CHOOSE 返回的 ,NULL,NULL
被当作一个参数!
问题:
- 为什么 C 预处理器将
,NULL,NULL
视为单个参数?
- 如何使 C 预处理器将
CHOOSE
的结果视为以逗号分隔的参数列表而不是单个参数?
注意:
- 我想知道为什么 C 预处理器不能像我期望的那样工作。
在我看来,您似乎正在将 C 语言本身的直觉带回 C 预处理器,而这些直觉正在困扰您,因为 CPP 的工作方式不同。通常在 C 中,函数将 键入的值 作为参数。表达式不是类型值;他们得到评估以提供这些东西。因此,当您链接事物时,您最终得到的是一种 inner-out 评估;这塑造了你的直觉。例如,在计算 f(g(h(),h()),m())
时,f 被传递了两个参数,但它不能对 g(h(),h())
做任何事情;必须计算,结果是一个值,这是传递给 f 的参数。说hreturns1,mreturns7,greturns一个和,f一个乘积。然后 g 在 values 1 和 1 上求值。f 在 values 2 和 7 上求值。大多数 C 编码都使用这种语言,你得到习惯了这些内部表达式求值并将结果值传递给函数的想法。但这不是宏的工作方式。
在奇怪的宏调用世界中(措辞谨慎;我有意忽略条件指令),您的函数不采用键入的值;他们采用 令牌序列 。 CPP 确实为您匹配括号,这意味着 F(())
是使用参数 ()
调用 F
,而不是使用参数 (
后跟 [=] 的调用21=] 令牌。但是在宏观领域,F(G(H(),H()),M())
使用两个参数调用 F
。参数1是token序列G(H(),H())
;参数 2 是标记序列 M()
。我们不会计算表达式 G
来获得类型值,因为没有类型值;只有令牌序列。
像宏这样的函数的宏调用步骤从(6.10.3.1)参数替换开始(a.s。)。在 a.s 期间,CPP 首先查看正在调用的宏的定义,并记录宏的 参数 在其 替换列表 。对于任何未被字符串化且未参与粘贴的此类提及,CPP 评估相应的参数,其评估结果将替换替换列表中参数的这些合格提及。接下来,CPP 将 (6.10.3.2) 字符串化并以无特定顺序粘贴 (6.10.3.3)。完成所有这些后,生成的替换列表 (6.10.3.4) 将进行重新扫描和进一步替换 (r.a.f.r),顾名思义,重新扫描以进行进一步替换;在此重新扫描期间,特定宏被暂时禁用("painted blue",根据 6.10.3.4p2)。
让我们来看看这个;我将忽略您正在使用语言扩展(gcc?clang?)的事实,您在其中调用参数数量不足的可变参数宏(无论如何您都不是故意这样做的)。我们开始于:
FX()
调用 FX
,带有一个空标记列表的单个参数(请注意,对于 CPP,零参数只有在您使用零参数定义宏时才有意义;F()
是用一个空参数调用就像 F(,)
用两个空参数调用一样)。那么a.s。发生了,这将 FX
的替换列表从这个...转换为这个:
RM_FIRST (FF(__VA_ARGS__) ()) => RM_FIRST (FF() ())
跳过stringification/pastes,因为有none,然后我们做r.a.f.r。将 RM_FIRST
识别为宏。 RM_FIRST
有一个参数:FF() ()
。所以我们跳入递归级别 2...调用 RM_FIRST
.
RM_FIRST
本身的调用以 a.s 开始。假设可变参数部分被视为空,我们有与 FF() ()
相关联的参数 x,但这正是您的直觉真正失败的地方。 x 未在替换列表中提及,因此 FF() ()
没有任何变化。那是 a.s。为你。根据任何扩展应用 __VA_ARGS__
处理,就好像它是空的,我们只是得到这个:
__VA_ARGS__ =>
...IOW,那里什么都没有了。所以我们基本上完成了。
我猜你是 C-function-intuiting 这个;在这样做时,您期望 FF() ()
进行评估,并将结果作为参数传递给 RM_FIRST
,但这不是宏评估的方式。
但是,您可以通过间接方式实现这一点。如果您改为这样做:
#define RM_FIRST(...) RM_FIRST_I(__VA_ARGS__)
#define RM_FIRST_I(x,...) __VA_ARGS__
...我们回到调用 RM_FIRST
时,我们有一个不同的故事。这里,FF() ()
是可变参数列表的一部分,并且提到了 __VA_ARGS__
。所以在那 a.s。步骤,我们会得到:
RM_FIRST_I(__VA_ARGS__) => RM_FIRST_I( () () ,NULL,NULL ())
(只是字面意思...我猜多余的垃圾是您诊断的一部分;但我很确定您知道在哪里删除多余的 ()'s)。然后,在 r.a.f.r 期间,我们看到 RM_FIRST_I
被调用,故事就是这样。
我想根据参数的数量选择两个函数之一:
nargs = 0
----> f1nargs > 0
----> f2.
宏执行以下操作:获取第一个参数,然后如果没有提供参数,它将添加两个逗号“,NULL,NULL
”。然后它将 select 返回的参数列表中的第二个参数。
例如:
f("Hello, world%i%s", x , s)
----> "Hello, world%i%s
" ---->void
f()
---->,NULL,NULL
---->NULL
所以我可以根据参数的数量得到 null 或 void。
这是宏:
#define FIRST(a, ...) a
#define COMMA_IF_PARENS(...) ,NULL,NULL
#define IS_EMPTY(...) COMMA_IF_PARENS __VA_ARGS__ ()
#define RM_FIRST(x, ...) __VA_ARGS__
#define CHOOSE(...) IS_EMPTY(FIRST(__VA_ARGS__))
#define ff_(...)() CHOOSE (__VA_ARGS__)
#define FF(...)() ff_(__VA_ARGS__) ()
#define FX(...) RM_FIRST (FF(__VA_ARGS__) ())
FF 宏的输出:
FF()
---->,((void*)0),((void*)0);
FF("Hello, world%i%s")
---->COMMA_IF_PARENS "Hello, world%i%s" ();
FX 宏的输出:
FX()
--->void
FX("Hello, world%i%s")
---->void
预期外汇输出:
FX()
---->NULL
FX("Hello, world%i%s")
---->void
问题是从 CHOOSE 返回的 ,NULL,NULL
被当作一个参数!
问题:
- 为什么 C 预处理器将
,NULL,NULL
视为单个参数? - 如何使 C 预处理器将
CHOOSE
的结果视为以逗号分隔的参数列表而不是单个参数?
注意:
- 我想知道为什么 C 预处理器不能像我期望的那样工作。
在我看来,您似乎正在将 C 语言本身的直觉带回 C 预处理器,而这些直觉正在困扰您,因为 CPP 的工作方式不同。通常在 C 中,函数将 键入的值 作为参数。表达式不是类型值;他们得到评估以提供这些东西。因此,当您链接事物时,您最终得到的是一种 inner-out 评估;这塑造了你的直觉。例如,在计算 f(g(h(),h()),m())
时,f 被传递了两个参数,但它不能对 g(h(),h())
做任何事情;必须计算,结果是一个值,这是传递给 f 的参数。说hreturns1,mreturns7,greturns一个和,f一个乘积。然后 g 在 values 1 和 1 上求值。f 在 values 2 和 7 上求值。大多数 C 编码都使用这种语言,你得到习惯了这些内部表达式求值并将结果值传递给函数的想法。但这不是宏的工作方式。
在奇怪的宏调用世界中(措辞谨慎;我有意忽略条件指令),您的函数不采用键入的值;他们采用 令牌序列 。 CPP 确实为您匹配括号,这意味着 F(())
是使用参数 ()
调用 F
,而不是使用参数 (
后跟 [=] 的调用21=] 令牌。但是在宏观领域,F(G(H(),H()),M())
使用两个参数调用 F
。参数1是token序列G(H(),H())
;参数 2 是标记序列 M()
。我们不会计算表达式 G
来获得类型值,因为没有类型值;只有令牌序列。
像宏这样的函数的宏调用步骤从(6.10.3.1)参数替换开始(a.s。)。在 a.s 期间,CPP 首先查看正在调用的宏的定义,并记录宏的 参数 在其 替换列表 。对于任何未被字符串化且未参与粘贴的此类提及,CPP 评估相应的参数,其评估结果将替换替换列表中参数的这些合格提及。接下来,CPP 将 (6.10.3.2) 字符串化并以无特定顺序粘贴 (6.10.3.3)。完成所有这些后,生成的替换列表 (6.10.3.4) 将进行重新扫描和进一步替换 (r.a.f.r),顾名思义,重新扫描以进行进一步替换;在此重新扫描期间,特定宏被暂时禁用("painted blue",根据 6.10.3.4p2)。
让我们来看看这个;我将忽略您正在使用语言扩展(gcc?clang?)的事实,您在其中调用参数数量不足的可变参数宏(无论如何您都不是故意这样做的)。我们开始于:
FX()
调用 FX
,带有一个空标记列表的单个参数(请注意,对于 CPP,零参数只有在您使用零参数定义宏时才有意义;F()
是用一个空参数调用就像 F(,)
用两个空参数调用一样)。那么a.s。发生了,这将 FX
的替换列表从这个...转换为这个:
RM_FIRST (FF(__VA_ARGS__) ()) => RM_FIRST (FF() ())
跳过stringification/pastes,因为有none,然后我们做r.a.f.r。将 RM_FIRST
识别为宏。 RM_FIRST
有一个参数:FF() ()
。所以我们跳入递归级别 2...调用 RM_FIRST
.
RM_FIRST
本身的调用以 a.s 开始。假设可变参数部分被视为空,我们有与 FF() ()
相关联的参数 x,但这正是您的直觉真正失败的地方。 x 未在替换列表中提及,因此 FF() ()
没有任何变化。那是 a.s。为你。根据任何扩展应用 __VA_ARGS__
处理,就好像它是空的,我们只是得到这个:
__VA_ARGS__ =>
...IOW,那里什么都没有了。所以我们基本上完成了。
我猜你是 C-function-intuiting 这个;在这样做时,您期望 FF() ()
进行评估,并将结果作为参数传递给 RM_FIRST
,但这不是宏评估的方式。
但是,您可以通过间接方式实现这一点。如果您改为这样做:
#define RM_FIRST(...) RM_FIRST_I(__VA_ARGS__)
#define RM_FIRST_I(x,...) __VA_ARGS__
...我们回到调用 RM_FIRST
时,我们有一个不同的故事。这里,FF() ()
是可变参数列表的一部分,并且提到了 __VA_ARGS__
。所以在那 a.s。步骤,我们会得到:
RM_FIRST_I(__VA_ARGS__) => RM_FIRST_I( () () ,NULL,NULL ())
(只是字面意思...我猜多余的垃圾是您诊断的一部分;但我很确定您知道在哪里删除多余的 ()'s)。然后,在 r.a.f.r 期间,我们看到 RM_FIRST_I
被调用,故事就是这样。