获取类型推导结果

本文最后更新于:April 20, 2022 am

本系列博客主要讨论C++的类型推导系统

本博客介绍三种查看类型推导结果的方法

在软件开发的不同阶段,都可以获得类型推导的信息,关键是我们身处于哪个阶段,本博客从编写代码、编译和运行时三个阶段进行介绍

IDE

在编写代码期间,IDE常常会自动地显示类型推导的结果。不过,IDE的这些类型信息是来源于C++编译器,所以如果代码无法正常编译,那么类型推导结果也可能不会出现

1
2
3
4
const int theAnswer=42;

auto x = theAnswer;
auto y = &theAnswer;

对于简单的,诸如int的类型,这样就够了,但随着类型变得更复杂,IDE的信息就不那么管用了

编译器诊断

想要让编译器暴露某个类型推断结果,一个行之有效的方案是使用那个类型制造编译错误,以此来让编译器提示类型不匹配

如果我们希望让编译器告诉我们上述变量中xy的类型,可以如此操作:

1
2
3
4
5
template<typename T> class TD;
//声明一个类模板,但是不定义

TD<decltype(x)> xType;
TD<decltype(y)> yType;

由于我们没有给出类模板的定义,因此编译器将会发出类似这样的错误报告

编译器报错输出

运行时输出

如果要在控制台输出类型信息,那就必须等到运行时,但是可以自主控制所有的输出格式。这里的难点是要得到我们关心类型的字符串表达,并且这个表示得是易读易理解的

乍一看可以用typeid()std::type_info::name来解决

1
2
std::cout<<typeid(x).name()<<'\n';
std::cout<<typeid(y).name()<<'\n';

调用typeid函数,产生一个std::type_info对象,随后调用它的name方法可以得到一个C风格的字符串,用以概括类型信息

但是此函数不保证一定能返回”有意义“的字符串,但是编译器工作者已经在尽力返回有用的信息了。GNU和Clang编译器对于x会返回“i”表示int,对y返回“PKi”,表示“pointer to konst const”,当然,也可以为它们安装C++filt工具来翻译那些晦涩的信息。如果是Microsoft的编译器,将会显示“int”和“int const *”

考虑一个更复杂的情况

1
2
3
4
5
6
7
8
9
10
template<typename T> void f(const T& param);

std::vector<Widget> createVec();

const auto vw = createVec();

if(!vw.empty()){
f(&vw[0]);
......
}

这里既有用户自定义的类型Widget,又有STL容器,还有auto声明的变量,更接近于现实中我们迫切需要看到编译器的类型推导信息的情景,比如我们会希望知道函数模板f中的类型参数Tparam的最终类型

1
2
3
4
5
template <typename T> void f(const T& param){
using std::cout;
cout<<"T= "<<typeid(T).name()<<'\n';
cout<<"param= "<<typeid(param).name()<<'\n';
}

GNU和Clang的编译器环境下,将会输出

1
2
T=	PK6Widget	
param= PK6Widget

已经知道PK代表pointer to const,而此处的6,代表6后面的类名的字符数量(Widget有6个字符),也就是说它们的类型是const Widget*

Microsoft的编译器则是:

1
2
T=	class Widget const *
param= class Widget const *

等等,Tparam的类型居然是一样的?在函数模板f中,param可是声明为const T&的呀?

不幸的是,std::type_info::name的结果不总是可靠的,这就是反例之一。更近一步说,它们在这里必须是错的,因为std::type_info::name函数声明指出,会像一个按值传递的函数模板一样对待参数。在这篇博客中,我们已经讨论过,当函数模板的参数是按值传递时,如果实参是引用,那么引用的符号将会被忽略;此外,如果移除引用符号之后,还有const或者volatile关键字,那么这两者也被一并忽略

param的类型,在此处为const Widget * const &,被反射成const Widget* 也就不无道理。首先忽略了其引用符号,随后又忽略了它本身不可变的const符号(右侧的const),最后变成了const Widget*

更糟糕的事,IDE在此时报告的类型同样也不可靠,至少说,是不太有用。在此例中,我所用的IDE将T报告成

1
2
3
const std::_Simple_types<std::_Wrap_alloc<std::_Vec_base_types<Widget, 
std::allocator<Widget>
>::_Alloc>::value_type>::value_type *

对于param,报告的则是

1
const std::_Simple_types<...>::value_type *const &

param类型看起来比T的类型更人畜无害一些,但是“…”到底意味着什么呢?其实这是编译器的一种语言,它是在说,”既然T已经有这么一大堆了,那在param的类型中我就懒得再显示一次了吧!“

使用boost库

当知道std::type_info::name和IDE的问题之后,Boost 的TypeIndex库就像一个救星

1
2
3
4
5
6
7
#include <boost/type_index.hpp>
template<typename T> void f(const T& param){
using std::cout;
using boost::typeindex::type_id_with_cvr;
cout<<"T= "<<type_id_with_cvr<T>().pretty_name()<<'\n';
cout<<"param= "<<type_id_with_cvr<decltype(param)>().pretty_name()<<'\n';
}

type_id_with_cvr能够正常工作的秘密就在于,它可以在不忽略constvolatile和引用的情况下,拿到类型参数。函数返回boost::typeindex::type_index对象,方法pretty_name返回一个std::string对象,以提供友好的字符串表示

在GNU和Clang编译器下,使用Boost.TypeIndex来替代type_id,得到

1
2
T=	Widget const*
param= Widget const* const&

Microsoft的编译器会返回

1
2
T=	class Widget const *
param= class Widget const * const &

令人惊喜的一致!

总结

可以通过IDE、制造编译错误以及使用Boost的TypeIndex库来查看类型推导结果

一些工具的结果不能保证准确性和可用性,最重要的还是要理解C++的类型推导规则


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!