我们可以有递归宏吗?
Posted
技术标签:
【中文标题】我们可以有递归宏吗?【英文标题】:Can we have recursive macros? 【发布时间】:2012-09-08 23:29:09 【问题描述】:我想知道我们是否可以在 C/C++ 中使用递归宏?如果是,请提供示例。
第二件事:为什么我不能执行下面的代码?我在做什么错误?是因为递归宏吗?
# define pr(n) ((n==1)? 1 : pr(n-1))
void main ()
int a=5;
cout<<"result: "<< pr(5) <<endl;
getch();
【问题讨论】:
C 宏是文本宏。如果宏是递归的,你总是会构建一个无限的表达式,因为宏除了'replace this with that' 之外什么也做不了 @Cubic:实际上宏可以做更多的事情。参数引用、文本连接和后续定义的宏的迭代替换。但不是递归。 我不确定WHY您是否愿意这样做。如果您打算在编译时进行递归计算,您可能会对可变参数模板(新 C++ 标准的新特性)感兴趣。 否,但另一方面,模板是图灵完备的。***.com/questions/189172/c-templates-turing-complete 【参考方案1】:宏不会直接递归扩展,但有一些变通方法。当预处理器扫描并展开pr(5)
:
pr(5)
^
它会创建一个禁用上下文,以便当它再次看到pr
时:
((5==1)? 1 : pr(5-1))
^
无论我们尝试什么,它都会变成蓝色,并且无法再扩展。但是我们可以通过使用延迟表达式和一些间接来防止我们的宏变成蓝色:
# define EMPTY(...)
# define DEFER(...) __VA_ARGS__ EMPTY()
# define OBSTRUCT(...) __VA_ARGS__ DEFER(EMPTY)()
# define EXPAND(...) __VA_ARGS__
# define pr_id() pr
# define pr(n) ((n==1)? 1 : DEFER(pr_id)()(n-1))
所以现在它会像这样展开:
pr(5) // Expands to ((5==1)? 1 : pr_id ()(5 -1))
这是完美的,因为pr
从未被涂成蓝色。我们只需要应用另一个扫描以使其进一步扩展:
EXPAND(pr(5)) // Expands to ((5==1)? 1 : ((5 -1==1)? 1 : pr_id ()(5 -1 -1)))
我们可以应用两次扫描以使其进一步扩展:
EXPAND(EXPAND(pr(5))) // Expands to ((5==1)? 1 : ((5 -1==1)? 1 : ((5 -1 -1==1)? 1 : pr_id ()(5 -1 -1 -1))))
但是,由于没有终止条件,我们永远无法应用足够的扫描。我不确定您想要完成什么,但如果您对如何创建递归宏感到好奇,这里有一个如何创建递归重复宏的示例。
首先应用大量扫描的宏:
#define EVAL(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL1(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL2(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL3(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
#define EVAL4(...) EVAL5(EVAL5(EVAL5(__VA_ARGS__)))
#define EVAL5(...) __VA_ARGS__
接下来,一个对模式匹配有用的 concat 宏:
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__
递增和递减计数器:
#define INC(x) PRIMITIVE_CAT(INC_, x)
#define INC_0 1
#define INC_1 2
#define INC_2 3
#define INC_3 4
#define INC_4 5
#define INC_5 6
#define INC_6 7
#define INC_7 8
#define INC_8 9
#define INC_9 9
#define DEC(x) PRIMITIVE_CAT(DEC_, x)
#define DEC_0 0
#define DEC_1 0
#define DEC_2 1
#define DEC_3 2
#define DEC_4 3
#define DEC_5 4
#define DEC_6 5
#define DEC_7 6
#define DEC_8 7
#define DEC_9 8
一些对条件有用的宏:
#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)
#define NOT(x) CHECK(PRIMITIVE_CAT(NOT_, x))
#define NOT_0 ~, 1,
#define COMPL(b) PRIMITIVE_CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0
#define BOOL(x) COMPL(NOT(x))
#define IIF(c) PRIMITIVE_CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t
#define IF(c) IIF(BOOL(c))
#define EAT(...)
#define EXPAND(...) __VA_ARGS__
#define WHEN(c) IF(c)(EXPAND, EAT)
把它们放在一起,我们可以创建一个重复宏:
#define REPEAT(count, macro, ...) \
WHEN(count) \
( \
OBSTRUCT(REPEAT_INDIRECT) () \
( \
DEC(count), macro, __VA_ARGS__ \
) \
OBSTRUCT(macro) \
( \
DEC(count), __VA_ARGS__ \
) \
)
#define REPEAT_INDIRECT() REPEAT
//An example of using this macro
#define M(i, _) i
EVAL(REPEAT(8, M, ~)) // 0 1 2 3 4 5 6 7
所以,是的,通过一些变通方法,您可以在 C/C++ 中使用递归宏。
【讨论】:
在 gcc 4.8.3 中使用-std=c99
尝试此操作会导致 OBSTRUCT(REPEAT_INDIRECT) ()
: error: 'REPEAT_INDIRECT' undeclared here (not in a function)
行出错。将 REPEAT_INDIRECT 的定义移到 REPEAT 无法修复之前。
预处理器的输出是什么?
OBSTRUCT 宏在这里没有展开,因为它没有在这里定义。 @Paul 在 his original blog post 中定义它。
优雅的解决方案。这实际上是 lamda 演算。
很好的解决方案,但我无法让它在 VS 上运行,似乎 EAT 不起作用,并且总是留下最后一次迭代的 REPEAT(0, macro)
。【参考方案2】:
您的编译器可能提供了仅预处理而不实际编译的选项。如果您试图在宏中查找问题,这将非常有用。例如使用g++ -E
:
> g++ -E recursiveMacro.c
# 1 "recursiveMacro.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "recursiveMacro.c"
void main ()
int a=5;
cout<<"result: "<< ((5==1)? 1 : pr(5 -1)) <<endl;
getch();
如您所见,它不是递归的。 pr(x)
在预处理期间仅被替换一次。需要记住的重要一点是,预处理器所做的只是盲目地将一个文本字符串替换为另一个文本字符串,它实际上并不评估像 (x == 1)
这样的表达式。
您的代码无法编译的原因是 pr(5 -1)
没有被预处理器替换,因此它最终在源代码中作为对未定义函数的调用。
【讨论】:
为什么 pr(5-1) 被视为未定义的函数调用?我已经定义了一个宏,所以它应该进一步扩展为: ((5-1==1)? 1 : pr(5-1-1)) .... @user1367292 不,你不能有递归宏。如果它确实一直用pr(x-1)
替换pr(x)
,它将无限循环pr(x-1)
、pr(x-1-1)
、pr(x-1-1-1)
等...
veredesmarald -- 那么,你的意思是说“我们不能有递归宏吗?”。另外...是否有任何解决方法可以实现这一目标?
@user1367292 不,你不能。您提出的建议在预处理器的上下文中没有意义。当你所做的只是用它自己+一些其他东西一遍又一遍地替换一个字符串时,你怎么会为你的递归找到一个基本情况?
veredesmarald -- 谢谢 :-) 知道了。【参考方案3】:
您不不应该在 C 或 C++ 中使用递归宏。
来自 C++ 标准的相关语言,第 16.3.4 节第 2 段:
如果在替换列表的扫描过程中找到被替换的宏的名称(不包括源文件的其余预处理标记),则不会替换它。此外,如果任何嵌套替换遇到被替换的宏的名称,它不会被替换。这些未替换的宏名称预处理标记不再可用于进一步替换,即使它们稍后在本应替换宏名称预处理标记的上下文中(重新)检查。
这种语言有一些回旋余地。对于多个相互调用的宏,有一个灰色区域,该措辞并没有完全说明应该做什么。关于这个语言律师问题,有一个针对 C++ 标准的活跃问题;见http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#268。
忽略语言律师问题,每个编译器供应商都理解其意图:
C 或 C++ 中不允许使用递归宏。
【讨论】:
【参考方案4】:您很可能无法执行它,因为您无法编译它。此外,如果它能够正确编译,它总是会返回 1。你的意思是 (n==1)? 1 : n * pr(n-1)
。
宏不能递归。根据第 16.3.4.2 章(感谢 Loki Astari),如果在替换列表中找到当前宏,则保持原样,因此您的定义中的 pr
不会更改:
如果在此扫描期间找到被替换的宏的名称 替换列表(不包括源文件的其余部分 处理令牌),它不会被替换。此外,如果任何嵌套 替换遇到被替换的宏的名称,它不是 更换。这些不可替换的宏名称预处理标记不是 更长的时间可用于进一步更换,即使它们稍后再更换 在该宏名称预处理标记的上下文中(重新)检查 否则会被替换。
您的电话:
cout<<"result: "<< pr(5) <<endl;
被预处理器转换成:
cout<<"result: "<< (5==1)? 1 : pr(5-1) <<endl;
在此期间,pr
宏的定义“丢失”,编译器显示类似“'pr' is not declared in this scope (fact)”之类的错误,因为没有名为 pr
的函数。
不鼓励在 C++ 中使用宏。为什么不直接写一个函数呢?
在这种情况下,您甚至可以编写一个模板函数,以便在编译时解析它,并表现为一个常量值:
template <int n>
int pr() pr<n-1>();
template <>
int pr<1>() return 1;
【讨论】:
我的目的是了解递归宏。我不是在寻找一种更好的方法来打印一些价值......我不确定我们是否可以有一个递归宏。 你的论点有缺陷。如果您的宏包含其他宏,则重复宏替换算法(直到不再进行替换)。所以它可能会做递归宏。 但是语言规范明确禁止这样做,因为一旦宏被替换,它就会从潜在列表中删除以供后续替换(在同一行内)。 谢谢,已修复,不知道这个规则。【参考方案5】:您不能在 C 或 C++ 中使用递归宏。
【讨论】:
好吧..我的第一个疑问很清楚,你不能有递归宏。问题中我的示例代码中的错误怎么办...??? 你没有说你得到了什么错误,但是在pr
宏中递归使用的pr
不会得到扩展,并且可能导致“未定义函数”错误或类似的错误.以上是关于我们可以有递归宏吗?的主要内容,如果未能解决你的问题,请参考以下文章
我可以使用 Access VBA 来确定表是不是有数据宏吗?
我可以在 Visual Studio 2012/2013/2015/2017/2019 中录制/播放宏吗?