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

2014-11-24 12:59:00 · 作者: · 浏览: 4
地球人都知道C++里有一个typeid操作符可以用来获取一个类型/表达式的名称:

std::cout << typeid(int).name() << std::endl;

但是这个name()的返回值是取决于编译器的,在vc和gcc中打印出来的结果如下:

int // vc
i   // gcc

一个稍微长一点的类型名称,比如:

class Foo {};
std::cout << typeid(Foo*[10]).name() << std::endl;


打出来是这个效果:

class Foo * [10] // vc
A10_P3Foo        // gcc

(话说gcc您的返回结果真是。。)
当然了,想在gcc里得到和微软差不多显示效果的方法也是有的,那就是使用__cxa_demangle:

char* name = abi::__cxa_demangle(typeid(Foo*[10]).name(), nullptr, nullptr, nullptr);
std::cout << name << std::endl;
free(name);

显示效果:

Foo* [10]

先不说不同编译器下的适配问题,来看看下面这个会打印出啥:

// vc
std::cout << typeid(const int&).name() << std::endl;
 
// gcc
char* name = abi::__cxa_demangle(typeid(const int&).name(), nullptr, nullptr, nullptr);
std::cout << name << std::endl;
free(name);

显示效果:

int // vc
int // gcc

可爱的cv限定符和引用都被丢掉了=.=
如果直接在typeid的结果上加上被丢弃的信息,对于一些类型而言(如函数指针引用)得到的将不是一个正确的类型名称。
想要获得一个类型的完整名称,并且获得的名称必须要是一个正确的类型名称,应该怎样做呢?

一、如何检查C++中的类型

我们需要一个泛型类,用特化/偏特化机制静态检查出C++中的各种类型,并且不能忽略掉类型限定符(type-specifiers)和各种声明符(declarators)。
先来考虑一个最简单的类模板:

template 
  
   
struct check
{
    // ...
};
  


假如在它的基础上特化,需要写多少个版本呢?我们可以稍微实现下试试:

template 
  
    struct check
   
    ; template 
    
      struct check
     
      ; template 
      
        struct check
       
        ; template 
        
          struct check
         
          ; template 
          
            struct check
           
            ; template 
            
              struct check
             
              ; template 
              
                struct check
               
                ; template 
                
                  struct check
                 
                  ; template 
                  
                    struct check
                   
                    ; template 
                    
                      struct check
                     
                      ; template 
                      
                        struct check
                       
                        ; template 
                        
                          struct check
                         
                          ; template 
                          
                            struct check
                           
                            ; template 
                            
                              struct check
                             
                              ; template 
                              
                                struct check
                               
                                ; template 
                                
                                  struct check
                                 
                                  ; template 
                                  
                                    struct check
                                   
                                    ; template 
                                    
                                      struct check
                                     
                                      ; template 
                                      
                                        struct check
                                       
                                        ; template 
                                        
                                          struct check
                                         
                                          ; template 
                                          
                                            struct check
                                           
                                            ; template 
                                            
                                              struct check
                                             
                                              ; template 
                                              
                                                struct check
                                               
                                                ; // ......
                                               
                                              
                                             
                                            
                                           
                                          
                                         
                                        
                                       
                                      
                                     
                                    
                                   
                                  
                                 
                                
                               
                              
                             
                            
                           
                          
                         
                        
                       
                      
                     
                    
                   
                  
                 
                
               
              
             
            
           
          
         
        
       
      
     
    
   
  

这还远远没有完。有同学可能会说了,我们不是有伟大的宏嘛,这些东西都像是一个模子刻出来的,弄一个宏批量生成下不就完了。

实际上当我们真的信心满满的动手去写这些宏的时候,才发现适配上的细微差别会让宏写得非常痛苦(比如&和*的差别,[]和[N]的差别,还有函数类型、函数指针、函数指针引用、函数指针数组、类成员指针、……)。当我们一一罗列出需要特化的细节时,不由得感叹C++类型系统的复杂和纠结。

但是上面的理由并不是这个思路的致命伤。
不可行的地方在于:我们可以写一个多维指针,或多维数组,类型是可以嵌套的。总不可能为每一个维度都特化一个模板吧。

不过正由于类型其实是嵌套的,我们可以用模板元编程的基本思路来搞定这个问题:

template 
  
    struct check
   
     : check
    
     ; template 
     
       struct check
      
        : check
       
        ; template 
        
          struct check
         
           : check
          
           ; template 
           
             struct check
            
              : check
             
              ; template 
              
                struct check
               
                 : check
                
                 ; template 
                 
                   struct check
                  
                    : check
                   
                    ; // ......
                   
                  
                 
                
               
              
             
            
           
          
         
        
       
      
     
    
   
  

一个简单的继承,就让特化变得simple很多。因为当我们萃取出一个类型,比如T *,之后的T其实是携带上了除*之外所有其他类型信息的一个类型。那么把这个T再重复投入check中,就会继续萃取它的下一个类型特征。

可以先用指针、引用的萃取来看看效果:

#include 
  
   
 
template 
   
     struct check { check(void) { std::cout << typeid(T).name(); } ~check(void) { std::cout << std::endl; } }; #define CHECK_TYPE__(OPT) \ template 
    
      \ struct check
     
       : check
      
        \ { \ check(void) { std::cout << " "#OPT; } \ }; CHECK_TYPE__(const) CHECK_TYPE__(volatile) CHECK_TYPE__(const volatile) CHECK_TYPE__(&) CHECK_TYPE__(&&) CHECK_TYPE__(*) int main(void) { check
       
        (); system("pause"); return 0; }
       
      
     
    
   
  


输出结果(vc):

void const volatile * const * &

很漂亮,是不是?当然,在gcc里这样输出,void会变成v,所以gcc下面要这样写check模板:

template 
  
   
struct check
{
    check(void)
    {
        char* real_name = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);
        std::cout << real_name;
        free(real_name);
    }
    ~check(void) { std::cout << std::endl; }
};
  

二、保存和输出字符串

我们可以简单的这样修改check让它同时支持vc和gcc:

template 
  
   
struct check
{
    check(void)
    {
#   if defined(__GNUC__)
        char* real_name = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);
        std::cout << real_name;
        free(real_name);
#   else
        std::cout << typeid(T).name();
#   endif
    }
    ~check(void) { std::cout << std::endl; }
};
  


但是到目前为止,check的输出结果都是无法保存的。比较好的方式是可以像typeid(T).name()一样返回一个字符串。这就要求check能够把结果保存在一个std::string对象里。
当然了,我们可以直接给check一个“std::string& out”类型的构造函数,但是这样会把输出的状态管理、字符的打印逻辑等等都揉在一起。因此,比较好的设计方法是实现一个output类,负责输出和维护状态。我们到后面就会慢慢感觉到这样做的好处在哪里。
output类的实现可以是这样:

class output
{
    bool is_compact_ = true;
 
    template 
  
   
    bool check_empty(const T&) { return false; }
    bool check_empty(const char* val)
    {
        return (!val) || (val[0] == 0);
    }
 
    template 
   
     void out(const T& val) { if (check_empty(val)) return; if (!i