Item 4: Know how to view deduced types.
这次是对 Effective Modern C++ Item 4 的学习笔记。
从前面的介绍我们知道,类型推导的结果有时候并不是非常明显就能得到,为了安全起见,我们最好能够自己亲自验证下类型推导是否符合我们的预期,在以下3个阶段我们能够获得一些类型推导的信息:
- 代码编辑阶段
- 编译阶段
- 运行阶段
IDE Editors
一般地,在编辑代码时候,你的IDE经常可以展示一些变量、函数、参数等类型。比如如下代码,当你使用鼠标指向 y1 和 y2 时候,会显示 y1 为 int 类型、y2 为 const int* 类型。
#include <iostream>
int main() {
const int x = 2;
auto y1 = x;
auto y2 = &x;
return 0;
}
IDE 之所以能够知道类型推导的结果,是因为 IDE 背后运行了 C++ 的编译器(至少是编译器的前端),这就要求你的代码或多或少是可编译状态。
对于简单的类型,IDE 的显示的信息通常是有用的,但是对于复杂类型,可能它的推导类型就不准了。
Compiler Diagnostics
可以通过导致编译错误的方式获取类型推导的信息,编译报错信息是很好的提示信息:
#include <iostream>
template<typename T>
class TD;
int main() {
const int x = 2;
auto y1 = x;
auto y2 = &x;
TD<decltype(y1)> y1Type;
TD<decltype(y2)> y2Type;
return 0;
}
上面的代码片段,实例化模板将导致编译报错,报错信息如下:
main.cpp:11:22: error: aggregate ‘TD<int> y1Type’ has incomplete type and cannot be defined
TD<decltype(y1)> y1Type;
^~~~~~
main.cpp:12:22: error: aggregate ‘TD<const int*> y2Type’ has incomplete type and cannot be defined
TD<decltype(y2)> y2Type;
从上面的报错信息可以获取到类型推导信息。
Runtime Output
通过允许下时打印类型是比较准确和靠谱的。第一种方式借助 typeid 控制输出:
#include <iostream>
int main() {
const int x = 2;
auto y1 = x;
auto y2 = &x;
std::cout << "y1: " << typeid(y1).name() << std::endl;
std::cout << "y1: " << typeid(y2).name() << std::endl;
return 0;
}
打印信息:
y1: i
y1: PKi
i 代表 int 类型, PKi 代码 pointor to const int。
这看起来还好,但是考虑下面的代码:
template<typename T> // template function to be called
void f(const T& param);
std::vector<Widget> createVec(); // factory function
const auto vw = createVec(); // init vw w/factory return
if (!vw.empty()) {
f(&vw[0]); // call f
...
}
使用 typeid 来输出 T 和 param 的类型,不论是哪个编译器,你都会得到错误的结果。例如微软的编译器会告诉你:二者的类型都是 class Widget const*。但是,param 的类型比 T 多一个 const &。这是由于我们这里 typeid 是值传递的,在用模板规则推导类型时,由 Item1 可知会丢失 const T& 中的 const 和引用属性。
幸好还有另一种方法,使用 Boost 库提供的方法,可以得到可靠的类型推导信息:
#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';
...`
}
模板函数 boost::typeindex::type_id_with_cvr 使用我们传递的类型实例化,而且它不会删去该类型已有的 const、volatile 和引用语义(所以后缀带有 “with_cvr”)。这个模板函数的返回结果是个boost::typeindex::type_index 对象,该对象的成员函数 pretty_name 会生成非常友好的 std::string来表示这个类型。
在 GNU 和 Clang 的编译器下运行结果为:
T = Widget const*
param = Widget const* const&
在微软的编译器下运行的结果一致:
T = class Widget const *
param = class Widget const * const &
总结
- 通常可以使用 IDE 编辑器、编译器报错信息和 Boost TypeIndex 库来查看已推断的类型。
- 一些工具的结果可能没有帮助或者不准确,还是要理解透彻 C++ 的类型推断规则。