《产生式元编程》第三章 替换蓝染概念纤悉
前两章主要集中于应用实践,理论概念都是蜻蜓点水,本章将重点放在这些概念原理上,深入讲解一下。
宏二段替换
源文件扫描后,宏被替换为目标内容,替换实际上分为两个阶段。
第一阶段的替换发生在参数替换之时。
当宏函数含有参数时,该参数会被替换。只有两种情况例外,一是 token 前面含有 #
或 ##
预处理标记,二是后面紧跟着 ##
预处理标记。
例如:
#define Def(x) x
#define Function(x, y) x ## y
#define Func(x) #x
#define Fun(x) ##x // Not allowed
#define Fn(x) x# // Not allowed
#define Defn(x, y) # x ## y // Not allowed
#define A() 1
Def(A()) // 1
Function(1, A()) // 1A()
Function(A(), 2) // Error!
Func(A()) // "A()"
第二阶段的替换发生在重新扫描之时。
此时宏的参数已被第一阶段的替换展开,展开后可能会存在新的宏,为了尽可能多地寻找后续的可替换内容,将再次扫描。
例如:
#define Foo(x) x(x)
#define Bar(x) #x
#define A() Bar
Foo(A()) // "Bar"
重新扫描并非只执行一次,而是会一直循环进行,尽可能替换更多内容。比如:
#define Foo(x) x(x)
#define Bar(x) x ## 1()
#define Bar1() hello
#define A() Bar
Foo(A()) // hello
需要注意,预处理器按顺序处理输入,对于之前处理过的 token,不会再作替换。例如:
// Example from ISO C
#define F(a) a
#define FUNC(a) (a+1)
/*
* The preprocessor works successively through the input without
* backing up through previous processed preprocessing tokens.
*/
F(FUNC) FUNC (3); // FUNC (3 +1);
此处,替换宏函数的参数列表,将 F(FUNC)
替换成 FUNC
, FUNC (3)
替换成 (3+1)
。由于 FUNC
已经被处理过,后续不会发生重新扫描,展开成 FUNC (3+1);
便停止处理。
具体处理步骤可表示如下:
/*
* detailed:
expand F={ FUNC } FUNC={ (3+1) }
remove FUNC (3+1)
* Final tokens output:
FUNC (3+1)
*/
若非函数宏,或无参宏函数,则不会发生参数替换,直接从扫描替换开始。通过前面章节的学习,大家清楚,这种循环进行的扫描替换,并非在任何时候都会进行下去。
什么时候会停止呢?便是蓝染时刻。
蓝染(blue paint)
blue paint 是 C 预处理器中的一个黑话,一次扫描中,若是替换的 token 引用了其自身,该 token 将被标记为不可处理状态。这个标记动作就称为 Painted blue。
名称缘由已不可考,据说是由 C 工程师标记 token 的墨水颜色而来,便延续着叫了。蓝染是本文为书写方便所采取的翻译,该词原为中国古代印染工艺的术语。
一个例子:
// Examples from ISO C
#define A A B C
#define B B C A
#define C C A B
A
/*
* detailed:
expand A={ A B C }
paint A={ a B C }
rescan A={ a B={ B C A } C }
paint A={ a B={ b C a } C }
rescan A={ a B={ b C={ C A B } a } C }
paint A={ a B={ b C={ c a b } a } C }
remove A={ a B={ b c a b a } C }
remove A={ a b c a b a C }
rescan A={ a b c a b a C={ C A B } }
paint A={ a b c a b a C={ c a B } }
rescan A={ a b c a b a C={ c a B={ B C A }}}
paint A={ a b c a b a C={ c a B={ b c a }}}
remove A={ a b c a b a C={ c a b c a }}
remove A={ a b c a b a c a b c a }
remove a b c a b a c a b c a
* Final tokens output:
A B C A B A C A B C A
*/
示例中,使用小写字母表示蓝染标记的 token,一次扫描实际指的是一个 token 的一次扫描,并非是说不会发生重新扫描。
宏预处理器按顺序逐个处理 token,展开、替换、蓝染、移除…… 因为ABC
相互引用,当所有 token 都被处理完成之后,它们也全部被蓝染标记。
再看一个例子:
#define Foo(X) 1 Bar
#define Bar(X) 2 Foo
Foo(X)(Y)(Z)
/*
* detailed:
expand Foo={ 1 Bar }(Y)(Z)
remove 1 Bar(Y)(Z)
rescan 1 Bar={ 2 Foo }(Z)
remove 1 2 Foo(Z)
rescan 1 2 Foo={ 1 Bar }
remove 1 2 1 Bar
* Final tokens output:
1 2 1 Bar
*/
此处,虽为宏函数,但参数却形同虚设,没有发生参数替换,也没有发生蓝染标记,就是不断扫描、替换、再扫描…… 重复进行,直到无可替换。
蓝染与递归
蓝染是 C 预处理器不支持递归的原因,知道了这个原理,再去看第二章的内容,便能够剖析入微,鞭辟入里。
这里摘出来一个简洁版本讲解:
#define RECURSIVE(x) x RECURSIVE(x)
RECURSIVE(1)
/*
* detailed:
expand RECURSIVE={ 1 RECURSIVE(1) }
paint RECURSIVE={ 1 recursive(1) }
remove 1 recursive(1)
* Final token output:
1 RECURSIVE(1)
*/
RECURSIVE(1)
处理完成之后被蓝染,致递归未始即终。
蓝染后的 token,即便强制展开,也无半点效果。
#define EVAL1(...) __VA_ARGS__
#define RECURSIVE(x) x RECURSIVE(x)
// Not working
EVAL1(RECURSIVE(1))
/*
* detailed:
expand EVAL1( RECURSIVE={ 1 RECURSIVE(1) } )
paint EVAL1( RECURSIVE={ 1 recursive(1) } )
remove EVAL1( 1 recursive(1) )
rescan 1 recursive(1)
* Final token output:
1 RECURSIVE(1)
*/
因为重新扫描会循环发生,所以即使你增加一个间接层,也不会对结果产生丝毫影响。
#define EVAL1(...) __VA_ARGS__
#define RECURSIVE(x) x _RECURSIVE()(x)
#define _RECURSIVE() RECURSIVE
// Not working
EVAL1(RECURSIVE(1))
/*
* detailed:
expand EVAL1( RECURSIVE={ 1 _RECURSIVE()(1) } )
rescan EVAL1( RECURSIVE={ 1 RECURSIVE(1) } )
paint EVAL1( RECURSIVE={ 1 recursive(1) } )
remove EVAL1( 1 recursive(1) )
rescan 1 recursive(1)
* Final token output:
1 RECURSIVE(1)
*/
因此,首先需要一种方法,能够禁止重新扫描。这种方法就是延迟扫描,基本代码逻辑如下所示:
#define EMPTY()
#define DEFER(id) id EMPTY()
#define Bar() 1
Bar() // 1
DEFER(Bar)() // Bar ()
/*
* DEFER(Bar)()
* detailed:
expand Bar EMPTY()()
rescan Bar ()
*/
第一节说过,预处理器按顺序处理输入,对于之前处理过的 token,不会再作处理。Bar
便是已经处理过的 token,不会再对它扫描。
将这个技术应用到递归代码中,便可以避免蓝染标记。
#define EMPTY()
#define DEFER(id) id EMPTY()
#define EVAL1(...) __VA_ARGS__
#define RECURSIVE(x) x DEFER(_RECURSIVE) ()(x)
#define _RECURSIVE() RECURSIVE
EVAL1(RECURSIVE(1))
/*
* detailed:
expand EVAL1( RECURSIVE={ 1 DEFER(_RECURSIVE) ()(1) } )
rescan EVAL1( RECURSIVE={ 1 _RECURSIVE ()(1) } )
remove EVAL1( 1 _RECURSIVE ()(1) )
rescan EVAL1={ 1 RECURSIVE(1) ) }
rescan EVAL1={ 1 RECURSIVE={1 DEFER(_RECURSIVE) ()(1) } }
rescan EVAL1={ 1 RECURSIVE={1 _RECURSIVE ()(1) } }
remove EVAL1={ 1 1 _RECURSIVE ()(1) }
remove 1 1 _RECURSIVE ()(1)
* Final token output:
1 1 _RECURSIVE ()(1)
*/
第二节说过,蓝染标记只在一次扫描期间存在,通过间接性加延迟扫描,RECURSIVE
便永远不会再被蓝染标记。但是,禁用重新扫描,我们也会因此失去循环扫描的能力,必须手动通过 EVAL1
来运行一次扫描。
手动调用扫描的次数和递归的深度成正比,一次次手动太过麻烦,故而一般会提前定义好多层扫描,以便后续使用。
#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__
有了该工具,便可基本模拟宏递归,本章只是穿插着讲原理,更多使用见第二章。
总的来说,宏预处理器的替换规则算不上晦涩,但相关资料寥寥无几,文章更是少得可怜,像是黑盒子,导致理解起来并不容易。本章对此进行了深入剖析,篇幅不长,主要是应用放在了第二章,这部分有一定难度,仍归为四星。