(笔者注:本篇的技巧,尤其是关于 typeid
和boost库用法的讲解非常实用)
- 分三种情形讨论如何获得类型推导的结果:编辑代码时获得,编译时获得和运行时获得。
IDE编辑器
- 多数现代IDE都有这类功能,当鼠标悬浮在实体上时,显示其类型。
- 这也说明IDE中通常带有能感知代码信息的编译器,或至少是编译器的前端。
- 对于简单的类型(如
int
),IDE提供的信息很有用。但后面我们会看到,当涉及的类型变复杂后,IDE展示的信息可能并不是非常有帮助。
编译器诊断信息(Diagnostics)
- 展示推断类型的一种有效方法就是让编译器报与类型相关的错(笔者:读到这儿着实笑了ww)。例如,声明但不定义一个模板类,想查看任何变量的类型,将其传给模板参数即可:
template<typename T>
class TD;
// x和y的类型会显示在编译错误信息中
TD<decltype(x)> xType;
TD<decltype(y)> yType;
运行时输出
- 问题在于如何创造一个类型的字符串表示(笔者注:大一时这一直是个困扰我的问题,网上学的typeid也不太好用,现在终于得到了一个答案…不过还是要说类型相关的功能至少在使用上,C++确实不如Java, C#, Python等语言方便)
- 解决方法1:使用C++标准的
typeid
。对任意对象使用typeid
返回一个std::type_info
对象,该对象有一个成员函数name()
,返回变量类型的const char*
字符串。name
函数的定义由实现决定,没有统一标准。 例如,MSVC编译器的结果就和GCC不同:
VS2022编译结果。类型的可读性较好。
VSCode使用g++编译结果。i代表int,PKi代表Pointer K(C)onst to int,可读性略差。这些名称源于编译器对类型的内部编码(name mangling机制),可以用
c++filt
工具解码。
- 以上设定过于简单。让我们向前一步,看看更接近实际应用场景的例子:
// 模板函数,打印T和param的类型名
template<typename T>
void f(const T& param)
{
cout << "T = " << typeid(T).name() << '\n';
cout << "param = " << typeid(param).name() << '\n';
}
// 某个自定义类
class Widget {};
// 生产Widget数组的工厂函数
vector<Widget> createVec()
{
return vector<Widget>{ Widget() }; // 假设返回的vector含一个元素
}
int main()
{
const auto vw = createVec();
if (!vw.empty()) {
f(&vw[0]);
}
return 0;
}
MSVC和GCC运行结果:
PK代表pointer-to-const,6是Widget类名的长度。详见name mangling规则。
- 问题来了:在模板中,
param
的类型为const T&
,既然如此,param
和T
的类型不应该是不同的吗?例如,T
为int
,param
应为const int&
。实际上,两种编译器都告诉我们,param
和T
的类型均为const Widget*
。 - 遗憾的是,
std::type_info::name
的结果并不可靠,因为它将类型以类似模板按值传递(Case 3)的推断规则处理,这导致类型的引用性、const
和volatile
都被丢失。实际上,param
的类型应为const Widget * const &
。 - 同样遗憾的是,IDE展示的类型信息可能同样不靠谱,或至少起不到什么帮助。对于此例,有些IDE将
T
和param
的类型报告为:
// T
const std::_Simple_types<std::_Wrap_alloc<std::_Vec_base_types<Widget,
std::allocator<Widget> >::_Alloc>::value_type>::value_type *
// param
const std::_Simple_types<...>::value_type *const &
param
看着没那么吓人,但其中的...
实际上是缩略。
- 笔者注:本例中以上问题在我的环境中没有得到复现。目前(2021.12)VS2022和VSCode都报告了正确的函数模板推导结果(不过,我确实有深刻印象VSCode在使用STL类型时经常提示各种大长串难以理解的类型标识符)。
- 主角登场!作者强力推荐
Boost
库中的Boost.TypeIndex
。以下为使用样例:
#include <boost/type_index.hpp> // boost库可在官网https://www.boost.org/下载
template<typename T>
void f(const T& param)
{
using std::cout;
using boost::typeindex::type_id_with_cvr;
// show T
cout << "T = "
<< type_id_with_cvr<T>().pretty_name()
<< '\n';
// show param's type
cout << "param = "
<< type_id_with_cvr<decltype(param)>().pretty_name()
<< '\n';
}
- 这种方法使用了
boost::typeindex::type_id_with_cvr
模板,其接受一个类型参数,并且不会抹除const
、volatile
和引用修饰符(因此得名with_cvr
)。返回结果是一个boost::typeindex::type_index
对象,其有一个成员函数pretty_name
,返回一个容易阅读的std::string
类型名称字符串。MSVC和GCC运行结果:
总结
- 推导出的类型可以通过IDE编辑器,编译错误信息和Boost TypeIndex库查看。
- 一些工具的结果可能既不准确也没什么用,因此,我们自身对于C++类型推导规则的理解仍是最重要的。