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

2014-11-24 12:59:00 · 作者: · 浏览: 7
s_compact_) sr_ += " "; using ss_t = std::ostringstream; sr_ += static_cast (ss_t() << val).str(); is_compact_ = false; } std::string& sr_; public: output(std::string& sr) : sr_(sr) {} output& operator()(void) { return (*this); } template output& operator()(const T1& val, const T&... args) { out(val); return operator()(args...); } output& compact(void) { is_compact_ = true; return (*this); } };

这个小巧的output类负责自动管理输出状态(是否增加空格)和输出的类型转换(使用std::ostringstream)。
上面的实现里有两个比较有意思的地方。
一是operator()的做法,采用了变参模板。这种做法让我们可以这样用output:

output out(str);
out("Hello", "World", 123, "!");


这种写法比cout的流操作符舒服多了。
二是operator()和compact的返回值。当然,这里可以直接使用void,但是这会造成一些限制。
比如说,我们想在使用operator()之后马上compact呢?若让函数返回自身对象的引用,就可以让output用起来非常顺手:

output out(str);
out.compact()("Hello", "World", 123, "!").compact()(" ");


check的定义和CHECK_TYPE__宏只需要略作修改就可以使用output类:

template 
  
   
struct check
{
    output out_;
    check(const output& out) : out_(out)
    {
#   if defined(__GNUC__)
        char* real_name = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);
        out_(real_name);
        free(real_name);
#   else
        out_(typeid(T).name());
#   endif
    }
};
 
#define CHECK_TYPE__(OPT) \
    template 
   
     \ struct check
    
      : check
     
       \ { \ using base_t = check
      
       ; \ using base_t::out_; \ check(const output& out) : base_t(out) { out_(#OPT); } \ };
      
     
    
   
  


为了让外部的使用依旧简洁,实现一个外敷函数模板是很自然的事情:

template 
  
   
inline std::string check_type(void)
{
    std::string str;
    check
   
     { str }; return std::move(str); } int main(void) { std::cout << check_type
    
     () << std::endl; system("pause"); return 0; }
    
   
  

如果我们想实现表达式的类型输出,使用decltype包裹一下就行了。

不知道看到这里的朋友有没有注意到,check在gcc下的输出可能会出现问题。原因是abi::__cxa_demangle并不能保证永远返回一个有效的字符串。
我们来看看这个函数的返回值说明:

“Returns: A pointer to the start of the NUL-terminated demangled name, or NULL if the demangling fails. The caller is responsible for deallocating this memory using free.”

所以说比较好的做法应该是在abi::__cxa_demangle返回空的时候,直接使用typeid(T).name()的结果。
一种健壮的写法可以像这样:

template 
  
   
struct check
{
    output out_;
    check(const output& out) : out_(out)
    {
#   if defined(__GNUC__)
        const char* typeid_name = typeid(T).name();
        auto deleter = [](char* p)
        {
            if (p) free(p);
        };
        std::unique_ptr
   
     real_name { abi::__cxa_demangle(typeid_name, nullptr, nullptr, nullptr), deleter }; out_(real_name   real_name.get() : typeid_name); # else out_(typeid(T).name()); # endif } };
   
  

上面我们通过使用std::unique_ptr配合lambda的自定义deleter,实现了一个简单的Scope Guard机制,来保证当abi::__cxa_demangle返回的非NULL指针一定会被free掉。

三、输出有效的类型定义

3.1 一些准备工作

上面的特化解决了cv限定符、引用和指针,甚至对于未特化的数组、类成员指针等都有还不错的显示效果,不过却无法保证输出的类型名称一定是一个有效的类型定义。比如说:

check_type
  
   (); // int [] *
  

原因是因为这个类型是一个指针,指向一个int[],所以会先匹配到指针的特化,因此*就被写到了最后面。
对于数组、函数等类型来说,若它们处在一个复合类型(compound types)中“子类型”的位置上,它们就需要用括号把它们的“父类型”给括起来。
因此我们还需要预先完成下面这些工作:

  • 1. 如何判断数组、函数等类型的特化处于check继承链中“被继承”(也就是某个类的基类)的位置上
  • 2. 圆括号()、方括号[],以及函数参数列表的输出逻辑

    上面的第1点,可以利用模板偏特化这种静态的判断来解决。比如说,给check添加一个默认的bool模板参数:

    template 
       
        
    struct check
    {
        // ...
    };
     
    #define CHECK_TYPE__(OPT) \
        template 
        
          \ struct check
         
           : check
          
            \ { \ using base_t = check
           
            ; \ using base_t::out_; \ check(const output& out) : base_t(out) { out_(#OPT); } \ };
           
          
         
        
       

    这个小小的修改就可以让check在继承的时候把父-子信息传递下去。

    接下来先考虑圆括号的输出逻辑。我们可以构建一个bracket类,在编译期帮我们自动处理圆括号:

    // ()
     
    template 
       
        
    struct bracket
    {
        output& out_;
     
        bracket(output& out, const char* = nullptr) : out_(out)
        { out_("(").compact(); }
     
        ~bracket(void)
        { out_.compact()(")"); }
    };
     
    template <>
    struct bracket
        
          { bracket(output& out, const char* str = nullptr) { out(str); } };
        
       

    在bracket里,不仅实现了圆括号的输出,其实还实现了一个编译期if的小功能。当不输出圆括号时,我们可以给bracket指