这样对于加了cv属性的数组而言,编译和显示才是正常的。
接下来,考虑[N],我们需要稍微修改一下上面的CHECK_TYPE_ARRAY__宏,让它可以同时处理[]和[N]:
#define CHECK_TYPE_ARRAY__(CV_OPT, BOUND_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_ARRAY_CV__(BOUND_OPT, ...) \ CHECK_TYPE_ARRAY__(, BOUND_OPT, ,##__VA_ARGS__) \ CHECK_TYPE_ARRAY__(const, BOUND_OPT, ,##__VA_ARGS__) \ CHECK_TYPE_ARRAY__(volatile, BOUND_OPT, ,##__VA_ARGS__) \ CHECK_TYPE_ARRAY__(const volatile, BOUND_OPT, ,##__VA_ARGS__)
这段代码里稍微用了点“preprocessor”式的技巧。gcc的__VA_ARGS__处理其实不那么人性化。虽然我们可以通过“,##__VA_ARGS__”,在变参为空时消除掉前面的逗号,但这个机制却只对第一层宏有效。当我们把__VA_ARGS__继续向下传递时,变参为空逗号也不会消失。
因此,我们只有用上面这种略显抽搐的写法来干掉第二层宏里的逗号。这个处理技巧也同样适用于vc。
然后,实现各种特化模板的时候到了:
#define CHECK_TYPE_PLACEHOLDER__ CHECK_TYPE_ARRAY_CV__(CHECK_TYPE_PLACEHOLDER__) #if defined(__GNUC__) CHECK_TYPE_ARRAY_CV__(0) #endif CHECK_TYPE_ARRAY_CV__(N, size_t N)
这里有个有意思的地方是:gcc里可以定义0长数组[0],也叫“柔性数组”。这玩意在gcc里不会适配到T[N]或T[]上,所以要单独考虑。
现在,我们适配上了所有的引用、数组,以及普通指针:
check_type这里看起来有点不一样的是多维数组的输出结果,每个维度都被括号限定了结合范围。这种用括号明确标明数组每个维度的结合优先级的写法,虽然看起来不那么干脆,不过在C++中也是合法的。(); // void const volatile * (&) [10] check_type (); // int (([1]) [2]) [3]
当然,如果觉得这样不好看,想搞定这个也很简单,稍微改一下CHECK_TYPE_ARRAY__就可以了:
#define CHECK_TYPE_ARRAY__(CV_OPT, BOUND_OPT, ...) \
template
\
struct check
: check
::value> \ { \ using base_t = check
::value>; \ using base_t::out_; \ \ bound
bound_; \ bracket
bracket_; \ \ check(const output& out) : base_t(out) \ , bound_ (out_) \ , bracket_(out_) \ {} \ };
这里使用了std::is_array来判断下一层类型是否仍旧是数组,如果是的话,则不输出括号。
3.3 函数(Functions)的处理
有了前面准备好的parameter,实现一个函数的特化处理非常轻松:
templatestruct check : check { using base_t = check ; using base_t::out_; parameter parameter_; bracket bracket_; check(const output& out) : base_t(out) , parameter_(out_) , bracket_ (out_) {} };
这里有一个小注意点:函数和数组一样,处于被继承的位置时需要加括号;parameter的构造时机应该在bracket的前面,这样可以保证它在bracket之后被析构,否则参数列表将被添加到错误位置上。
我们可以打印一个变态一点的类型来验证下正确性:
std::cout << check_type() << std::endl; // 输出:char (* (* const) (int const (&) [10])) [10] // 这是一个常函数指针,参数是一个常int数组的引用,返回值是一个char数组的指针
我们可以看到,函数指针已经被正确的处理掉了。这是因为一个函数指针会适配到指针上,之后去掉指针的类型将是一个正常的函数类型。
这里我们没有考虑stdcall、fastcall等调用约定的处理,如有需要的话,读者可自行添加。
3.4 类成员指针(Pointers to members)的处理
类成员指针的处理非常简单:
templatestruct check : check { using base_t = check ; using base_t::out_; check(const output& out) : base_t(out) { check { out_ }; out_.compact()("::*"); } };
显示效果:
class Foo {};
std::cout << check_type
() << std::endl;
// 输出:int (Foo::* const) [3]
// 这是一个常类成员指针,指向Foo里的一个int[3]成员
3.5 类成员函数指针(Pointers to member functions)的处理
其实我们不用做什么特别的处理,通过T C::*已经可以适配无cv限定符的普通类成员函数指针了。只是在vc下,提取出来的T却无法适配上T(P...)的特化。
这是因为vc中通过T C::*提取出来的函数类型带上了一个隐藏的thiscall调用约定。在vc里,我们无法声明或定义一个thiscall的普通函数类型,于是T C::*的特化适配无法完美的达到我们想要的效果。
所以,我们还是需要处理无cv限定的类成员函数指针。通过一个和上面T C::*的特化很像的特化模板,就可以处理掉一般的类成员函数指针:
templatestruct check : check { using base_t = check ; using base_t::out_; check(const output& out) : base_t(out) { check { out_ }; out_.compact()("::*"); } };
下面考虑带cv限定符的类成员函数指针。在开始书写后面的代码之前,我们需要先思考一下,cv限定符在类成员函数指针上的显示位置是哪里?答案当然是在函数的参数表后面。所以我们必须把cv限定符的输出时机放在T(P...)显示完毕之后。
因此想要正确的输出cv限定符,我们必须调整T(P...)特化的调用时机:
// Do output at destruct
struct at_destruct
{
output& out_;
const char