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

2014-11-24 12:59:00 · 作者: · 浏览: 10
* str_; at_destruct(output& out, const char* str = nullptr) : out_(out) , str_(str) {} ~at_destruct(void) { out_(str_); } void set_str(const char* str = nullptr) { str_ = str; } }; #define CHECK_TYPE_MEM_FUNC__(...) \ template \ struct check \ { \ at_destruct cv_; \ check base_; \ output& out_ = base_.out_; \ \ check(const output& out) \ : cv_(base_.out_) \ , base_(out) \ { \ cv_.set_str(#__VA_ARGS__); \ check { out_ }; \ out_.compact()("::*"); \ } \ }; CHECK_TYPE_MEM_FUNC__() CHECK_TYPE_MEM_FUNC__(const) CHECK_TYPE_MEM_FUNC__(volatile) CHECK_TYPE_MEM_FUNC__(const volatile)

上面这段代码先定义了一个at_destruct,用来在析构时执行“输出cv限定符”的动作;同时把原本处在基类位置上的T(P...)特化放在了第二成员的位置上,这样就保证了它将会在cv_之后才被析构。
这里要注意的是,at_destruct的构造在base_和out_之前,所以如果直接给cv_传递out_时不行的,这个时候out_还没有初始化呢。但是在这个时候,虽然base_同样尚未初始化,但base_.out_的引用却是有效的,因此我们可以给cv_传递一个base_.out_。
另外,at_destruct虽然定义了带str参数的构造函数,CHECK_TYPE_MEM_FUNC__宏中却没有使用它。原因是若在宏中使用#__VA_ARGS__作为参数,那么当变参为空时,#__VA_ARGS__前面的逗号在vc中不会被自动忽略掉(gcc会忽略)。

最后,来一起看看输出效果吧:

class Foo {};
std::cout << check_type
   
    () << std::endl;
// 输出:int (Foo::* const) (int, Foo &&, int) volatile
// 这是一个常类成员函数指针,指向Foo里的一个volatile成员函数
   

尾声

折腾C++的类型系统是一个很有意思的事情。当钻进去之后就会发现,一些原先比较晦涩的基本概念,在研究的过程中都清晰了不少。
check_type的实用价值在于,可以利用它清晰的看见C++中一些隐藏的类型变化。比如完美转发时的引用折叠:

class Foo {};
 
template 
   
    
auto func(T&&) -> T;
 
std::cout << check_type
    
     )>() << std::endl; std::cout << check_type
     
      )>() << std::endl; std::cout << check_type
      
       )>() << std::endl;
      
     
    
   

在上面实现check_type的过程中,用到了不少泛型,甚至元编程的小技巧,充分运用了C++在预处理期、编译期和运行期(RAII)的处理能力。虽然这些代码仅是学习研究时的兴趣之作,实际项目中往往typeid的返回结果就足够了,但上面的不少技巧对一些现实中的项目开发也有一定的参考和学习价值。

顺便说一下:上面的代码里使用了大量C++11的特征。若想在老C++中实现check_type,大部分的新特征也都可以找到替代的手法。只是适配函数类型时使用的变参模板,在C++98/03下实现起来实在抽搐。论代码的表现力和舒适度,C++11强过C++98/03太多了。


完整代码及测试下载请点击:check_type.zip

更多内容请访问:http://darkc.at