如何在C++中获得完整的类型名称(三)

2014-11-24 12:59:00 · 作者: · 浏览: 9
定一个其它的输出内容。
当然,不实现bracket,直接在check的类型特化里处理括号逻辑也可以,但是这样的话逻辑就被某个check特化绑死了。我们可以看到bracket的逻辑被剥离出来以后,后面所有需要输出圆括号的部分都可以直接复用这个功能。

然后是[]的输出逻辑。考虑到对于[N]类型的数组,还需要把N的具体数值输出来,因此输出逻辑可以这样写:

// [N]
 
template 
   
    
struct 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 ;”语句会被优化掉,只生成确定逻辑的汇编码。

最后,是函数参数的输出逻辑。函数参数列表需要使用变参模板适配,用编译期递归的元编程手法输出参数,最后在两头加上括号。
我们可以先写出递归的结束条件:

template 
   
    
struct parameter;
 
template 
    
      struct parameter
     
       { output& out_; parameter(output& out) : out_(out) {} ~parameter(void) { bracket
      
        { out_ }; } };
      
     
    
   

输出逻辑写在析构里的理由,和bound一致。结束条件是显然的:当参数包为空时,parameter将只输出一对括号。
注意到模板的bool类型参数,让我们在使用的时候需要这样写:

parameter
   
     parameter_;
   


这是因为bool模板参数混在变参里,指定默认值也是没办法省略true的。
稍微有点复杂的是参数列表的输出。一个简单的写法是这样:

template 
   
    
struct 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表达式来凸显逻辑:

template 
   
    
struct 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[]特化是很简单的:

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_) {} };
       
      
     
    
   


这时对于不指定数组长度的[]类型,输出结果如下:

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__)