当然,不实现bracket,直接在check的类型特化里处理括号逻辑也可以,但是这样的话逻辑就被某个check特化绑死了。我们可以看到bracket的逻辑被剥离出来以后,后面所有需要输出圆括号的部分都可以直接复用这个功能。
然后是[]的输出逻辑。考虑到对于[N]类型的数组,还需要把N的具体数值输出来,因此输出逻辑可以这样写:
// [N] templatestruct bound { output& out_; bound(output& out) : out_(out) {} ~bound(void) { if (N == 0) out_("[]"); else out_("[").compact() ( N ).compact() ("]"); } };
输出逻辑需要写在bound类的析构,而不是构造里。原因是对于一个数组类型,[N]总是写在最后面的。
这里在输出的时候直接使用了运行时的if-else,而没有再用特化来处理。是因为当N是一个编译期数值时,对于现代的编译器来说“if (N == 0) ; else ;”语句会被优化掉,只生成确定逻辑的汇编码。
最后,是函数参数的输出逻辑。函数参数列表需要使用变参模板适配,用编译期递归的元编程手法输出参数,最后在两头加上括号。
我们可以先写出递归的结束条件:
templatestruct parameter; template struct parameter { output& out_; parameter(output& out) : out_(out) {} ~parameter(void) { bracket { out_ }; } };
输出逻辑写在析构里的理由,和bound一致。结束条件是显然的:当参数包为空时,parameter将只输出一对括号。
注意到模板的bool类型参数,让我们在使用的时候需要这样写:
parameterparameter_;
这是因为bool模板参数混在变参里,指定默认值也是没办法省略true的。
稍微有点复杂的是参数列表的输出。一个简单的写法是这样:
templatestruct parameter { output& out_; parameter(output& out) : out_(out) {} ~parameter(void) { bracket bk { out_, "," }; (void)bk; check { out_ }; parameter { out_.compact() }; } };
parameter在析构的时候,析构函数的scope就是bracket的影响范围,后面的其它显示内容,都应该被包括在bracket之内,因此bracket需要显式定义临时变量bk;
check的调用理由很简单,因为我们需要显示出每个参数的具体类型;
最下面是parameter的递归调用。在把out_丢进去之前,我们需要思考下具体的显示效果。是希望打印出(P1, P2, P3)呢,还是(P1 , P2 , P3)?
在这里我们选择了逗号之前没有空格的第一个版本,因此给parameter传递的是out_.compact()。
对parameter的代码来说,看起来不明显的就是bracket的作用域了,check和parameter的调用其实是被bracket包围住的。为了强调bracket的作用范围,同时规避掉莫名其妙的“(void)bk;”手法,我们可以使用lambda表达式来凸显逻辑:
templatestruct parameter { output& out_; parameter(output& out) : out_(out) {} ~parameter(void) { [this](bracket &&) { check { out_ }; parameter { out_.compact() }; } (bracket { out_, "," }); } };
这样bracket的作用域一目了然,并且和check、parameter的定义方式保持一致,同时也更容易看出来out_.compact()的意图。
3.2 数组(Arrays)的处理
好了,有了上面的这些准备工作,写一个check的T[]特化是很简单的:
templatestruct check : check { using base_t = check ; using base_t::out_; bound<> bound_; bracket bracket_; check(const output& out) : base_t(out) , bound_ (out_) , bracket_(out_) {} };
这时对于不指定数组长度的[]类型,输出结果如下:
check_type(); // int (*) []
当我们开始兴致勃勃的接着追加[N]的模板特化之前,需要先检查下cv的检查机制是否运作良好:
check_type();
尝试编译时,gcc会给我们吐出一堆类似这样的compile error:
error: ambiguous class template instantiation for 'struct check' check { str }; ^
检查了出错信息后,我们会惊讶的发现对于const int[]类型,竟然可以同时匹配T const和T[]。
这是因为按照C++标准ISO/IEC-14882:2011,3.9.3 CV-qualifiers,第5款:
“Cv-qualifiers applied to an array type attach to the underlying element type, so the notation “cv T,” where T is an array type, refers to an array whose elements are so-qualified. Such array types can be said to be more (or less) cv-qualified than other types based on the cv-qualification of the underlying element types.”
可能描述有点晦涩,不过没关系,在8.3.4 Arrays的第1款最下面还有一行批注如下:
“[ Note: An “array of N cv-qualifier-seq T” has cv-qualified type; see 3.9.3. ―end note ]”
意思就是对于const int[]来说,const不仅属于数组里面的int元素所有,同时还会作用到数组本身上。
所以说,我们不得不多做点工作,把cv限定符也特化进来:
#define CHECK_TYPE_ARRAY__(CV_OPT) \
template
\
struct check
: check
\ { \ using base_t = check
; \ using base_t::out_; \ \ bound<> bound_; \ bracket
bracket_; \ \ check(const output& out) : base_t(out) \ , bound_ (out_) \ , bracket_(out_) \ {} \ }; #define CHECK_TYPE_PLACEHOLDER__ CHECK_TYPE_ARRAY__(CHECK_TYPE_PLACEHOLDER__)