这是本系列的第四篇,终于我们要开始写一些有实际意义的东西了。这一节我们将介绍一些比较难看懂的宏的惯用法(黑魔法),本来准备一口气同时介绍如何实现图灵完备的宏的,结果发现篇幅太长。图灵完备宏放在下一篇中介绍了。那么就让我们开始学习真正的奇技淫巧吧!(๑•̀ㅂ•́)و✧
Token 粘贴
首先我们来复习下上一节的一个例子,这也是我们今天要介绍的第一个惯用法,token 粘贴的直接展开。
|
|
这里如过直接调用 ##
由于宏本身语言的限制 FOO_
并不会直接展开。而间接的(多加一层调用)调用后就能够克服宏本身的语言限制。后续我们将会看到,间接调用是宏中非常常见的一个技巧。
括号表达式
括号表达式这个名字是我自己起的。准确的叫法并不是很清楚,但是在宏当中却很常用。括号表达式指的是,将参数用括号括起来使用。这样利用 func-like 的宏不接括号不会被展开的特性可以完成一些有意思的东西。比如:
|
|
这个例子中如果参数带括号就会返回一个空。当然这个例子是没有什么意义的,但是在下边的例子中你会发现这一技巧将被反复的使用。
模式匹配
利用 Token 粘贴我们能够动态的创建不同的宏名。相当于我们可以描述一个宏名的结构,这和编程语言里的模式匹配正好不谋而合。我们可以利用这个特性我们可以实现类似 if 语句或者 switch 语句的功能。例如:
|
|
整个过程相当于 构造了不同的宏名,如果为 c 值为0就动态生成IIF_0
,反之亦然。相当于变相决定了宏的展开方向。此时如果 cond 等于 1 则执行第一个参数,如果等于0 则执行第二个参数。注意这里用了 PRIMITIVE_CAT
这个宏,因为我们一般希望参数能够完全展开。
此外同样的结构还能玩一些不同的小花样,比如取补
|
|
检测
在阅读很多库文件的源代码时(比如 boost),我们总会看到很多 XXX_CHECK
,XXX_PROBE
之类的宏。我第一次见到的时候非常的懵逼。从命名上根本看不出来作用是什么,而且还很难找出个准确的关键字去搜索。后来才明白这是一种宏中的惯用法,叫做「检测」(detection)。
检测给了我们这样一种能力,检测某个参数是否是特定的值。众所周知,在宏的基本语法中是不存在 if 这种东西的(宏不是预处理的#if)。但是根据不同的参数展现出不同的行为又是一个很常见的需求。因此,拥有判断某个参数是否是特定值的能力会给我们带来极大的便利。我们先来看看「检测」的写法:
|
|
可以看到这个技巧主要是利用了宏的可变长参数的特性。如果有 PROBE的调用 则第二个参数变为1,否则第二个参数保持不变为0。
具体应用的例子,比如可以检测参数是否为空:
|
|
如果 x 为空则能够匹配到IS_EMPTY_0
调用带有 PROBE()
的宏,否则就只是一串没有意义的字符串而已。利用一个小技巧就能够很方便的实现检测参数是否为空。注意此处我们使用了CAT
和PRIMITIVE_CAT
这是因为一般我们期望被判断参数应该被完全展开。
再比如,检测参数是否是括号表达式:
|
|
我们还可以利用这个技巧实现取反(C 语言中除了0取反是1其他的取反都是0)。
|
|
然后是转换为布尔值和 if 条件判断,限于篇幅就不推导了,请读者自行尝试:
|
|
这一节到此结束。本节从这里借了很多例子Cloak。下一节介绍如何实现图灵完备的宏。