条款4:了解如何观察推导出的类型
?
那些想要知道编译器推导出的类型的人通常分为两种,第一种是实用主义者,他们的动力通常来自于软件产生的问题(例如他们还在调试解决中),他们利用编译器进行寻找,并相信这个能帮他们找到问题的源头(they’re looking for insights into compilation that can help them identify the source of the problem.)。另一种是经验主义者,他们探索条款1-3所描述的推导规则,并且从大量的推导情景中确认他们预测的结果(对于这段代码,我认为推导出的类型将会是…),但是有时候,他们只是想简单的回答如果这样,会怎么样呢之类的问题?他们可能想知道如果我用一个万能引用(见条款26)替代一个左值的常量形参(例如在函数的参数列表中用T&&替代const T&)模板类型推导的结果会改变吗?
?
?
?
不管你属于哪一类(二者都是合理的),你所要使用的工具取决于你想要在软件开发的哪一个阶段知道编译器推导出的结果,我们将要讲述3种可行的方法:在编辑代码的时获得推导的类型,在编译时获得推导的类型,在运行时获得推导的类型。
?
?
?
IDE编辑器
?
IDE中的代码编辑器通常会在你将鼠标停留在程序实体program entities(例如变量,参数,函数等等)上的时候显示他们的类型。例如,下面的代码中
?
const int theAnswer = 42; ?
auto x = theAnswer;?
auto y = &theAnswer;
IDE编辑器很可能显示出x的类型是int,y的类型是const int*.
?
?
?
对于这个工作,你的代码不能过于复杂,因为是IDE内部的编译器让IDE提供了这一项信息,如果编译器不能充分理解并解析你的代码,产生类型推导的结果,它就无法告诉你类型推导的结果。
?
?
?
编译器的诊断
?
知道编译器对某一类型推导出的结果一个有效方法是让它产生一个编译期的错误,因为错误的报告肯定会提到导致错误的类型。
?
假如我们想要知道上一个代码中的x和y被推导出的类型,我们首先声明却不定义一个模板,代码会像下面这样:
?
template // 只有TD的声明;?
class TD; // TD == "Type Displayer"
尝试实例化这个模板会产生一个错误信息,因为没有模板的定义,想要查看x和y的类型只需要用它们的类型实例化TD
?
TD xType; // 引起错误的信息包括了?
TD yType; // ?x和y的leix?
// decltype的用法可以参看条款3
我使用这种形式的变量名:variableNameType,因为:它们趋向于产生足够有用的错误信息(I use variable names of the form variableNameType, because they tend to yield quite informative error messages.)对于上面的代码,其中一个编译器的错误诊断信息如下所示(我突出了我们想要的类型推导结果)
?
error: aggregate 'TD xType' has incomplete type and?
cannot be defined?
error: aggregate 'TDyType' has incomplete type?
and cannot be defined
?
另一个编译器提供了一样的信息,但是格式有所不同
?
error: 'xType' uses undefined class 'TD'?
error: 'yType' uses undefined class 'TD'
?
抛开格式上的不同,我所测试的所有编译器都提供了包括类型的信息的错误诊断信息。
?
?
?
运行时的输出
?
利用printf方法(并不是说我推荐你使用printf)显示类型的信息不能在运行时使用,但是它需要对输出格式的完全控制,难点是如何让变量的类型能以文本的方式合理的表现出来,你可能会觉得“没有问题”typeid和std::type_info会解决这个问题的,你认为我们可以写下下面的代码来知道x和y 的类型:
?
std::cout << typeid(x).name() << '\n'; // 显示x和y的?
std::cout << typeid(y).name() << '\n'; // 类型
这个方法依赖于typeid作用于一个对象上时,返回类型为std::type_info这一个事实,type_info有一个叫name的成员函数,提供了一个C风格的字符串(例如 const char*)来表示这个类型的名字
?
std::type_info的name并不保证返回的东西一定是清楚明了的,但是会尽可能的提供帮助,不同的编译器提供的程度各有不同,例如:GNU和Clang编译器将x的类型表示为”i”,将y的类型表示为”PKI”,一旦你了解i意味着int,pk意味着pointer to Konst const(两个编译器都提供一个
C++ filt工具,来对这些重整后的名字进行解码),理解编译器的输出将变得容易起来,Microsoft的编译器提供了更清楚的输出,x的类型是int,y的类型是int const*.
?
?
?
因为对x和y显示的结果是正确的,你可能会认为问题已经解决了,但是让我们不要过于轻率,看看下面这个更复杂的例子:
?
复制代码
template // 被调用的?
void f(const T& param); // 函数模板?
std::vector createVec(); // 工厂函数?
const auto vw = createVec(); // 用工厂函数来实例化vw?
if (!vw.empty()) {?
f(&vw[0]); // 调用f?
}
复制代码
当你想知道编译器推导出的类型是什么的时候,这段代码更具有代表性,因为它牵涉到了一个用户自定义类型widget,一个std容器std::vector,一个auto变量,例如,你可能想知道模板参数T的类型,和函数参数f的类型。
?
?
?
使用typeid看起来是非常直接的方法(Loosing typeid on the problem is straightforward.),仅仅是在f中对你想知道的类型加上一些代码
?
复制代码
template?
void f(const T& param)?
{?
using std::cout;?
cout << "T = " << typeid(T).name() << '\n'; // 显示T的类型?
cout << "param = " << typeid(param).name() << '\n'; // 显示参数Param的类型 ?
}
复制代码
GNU和Clang的执行结果是下面这样:
?
T = PK6Widget?
param = PK6Widget
?
我们已经知道PK意味着pointer to const,而6代表了类的名字中有多少个字母(Widget),所以这两个编译器告诉了我们T和param的类型都是const Widget*
?
Morcrosoft的编译器提供了下面的结果
?
T = class Widget const *?
param = class Widget const *
?