条款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 = ;
auto x = theAnswer;
auto y = &theAnswer;

IDE编辑器很可能显示出x的类型是int,y的类型是const int*.

对于这个工作,你的代码不能过于复杂,因为是IDE内部的编译器让IDE提供了这一项信息,如果编译器不能充分理解并解析你的代码,产生类型推导的结果,它就无法告诉你类型推导的结果。

编译器的诊断

知道编译器对某一类型推导出的结果一个有效方法是让它产生一个编译期的错误,因为错误的报告肯定会提到导致错误的类型。

假如我们想要知道上一个代码中的x和y被推导出的类型,我们首先声明却不定义一个模板,代码会像下面这样:

template<typename T> // 只有TD的声明;
class TD; // TD == "Type Displayer"

尝试实例化这个模板会产生一个错误信息,因为没有模板的定义,想要查看x和y的类型只需要用它们的类型实例化TD

TD<decltype(x)> xType; // 引起错误的信息包括了
TD<decltype(y)> 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<int> xType' has incomplete type and
cannot be defined

error: aggregate 'TD<const int *>yType' has incomplete type

and cannot be defined

另一个编译器提供了一样的信息,但是格式有所不同

error: 'xType' uses undefined class 'TD<int>'

error: 'yType' uses undefined class 'TD<const int *>'

抛开格式上的不同,我所测试的所有编译器都提供了包括类型的信息的错误诊断信息。

运行时的输出

利用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<typename T> // 被调用的
void f(const T& param); // 函数模板
std::vector<Widget> createVec(); // 工厂函数
const auto vw = createVec(); // 用工厂函数来实例化vw
if (!vw.empty()) {
f(&vw[]); // 调用f
}

当你想知道编译器推导出的类型是什么的时候,这段代码更具有代表性,因为它牵涉到了一个用户自定义类型widget,一个std容器std::vector,一个auto变量,例如,你可能想知道模板参数T的类型,和函数参数f的类型。

使用typeid看起来是非常直接的方法(Loosing typeid on the problem is straightforward.),仅仅是在f中对你想知道的类型加上一些代码

template<typename T>
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 *

这三个编译器都提供了一样的信息,这或许暗示了结果应该是准确的,但是让我们看的更细致一点,在模板f中,param的类型被声明为constT&,既然如此的话,param和T的类型一样难道不让人感到奇怪吗,如果T的类型是int,param的类型应该是const int&,看,一点都不一样。

令人悲哀的是std::type_info::name的结果并不是可依赖的,在这个例子中,三个编译器对于param的结果都是不正确的,此外,它们必须是错误的,因为标准(specification)规定被std::type_info::name处理的类型是被按照按值传递给模板对待的,像条款1解释的那样,这意味着如果类型本身是一个引用的话,引用部分是被忽略掉的,如果引用去掉之后还含有const,常量性也将被忽略掉,,这就是为什么const Widget* const &的类型被显示为const Widget*,首先类型的引用部分被忽略了,接着结果的常量性也被忽略了。

同样令人伤心的是,IDE提供的类型信息同样也是不可靠的,或者说不是那么的实用,对于这个例子,我所知道的编译器将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 &

这个显示没有T的那么吓人了,中间的…只是意味着IDE告诉你,我将T的类型显示用…替代了。

template<typename T>

void f(const T& param)

{

TD<T> TType; // elicit errors containing

TD<decltype(param)> paramType; // T's and param's types

}

我的理解是大多数显示在这里的东西是由于typedef造成的,一旦你通过typedef来获得潜在的类型信息,你会得到你所寻找的,但需要做一些工作来消除IDE最初显示出的一些类型,幸运的话, 你的IDE编辑器会对这种代码处理的更好。

(My understanding is that most of what’s displayed here is typedef cruft and that

once you push through the typedefs to get to the underlying type information,

you get what you’re looking for, but having to do that work pretty much eliminates

any utility the display of the types in the IDE originally promised. With any luck,

your IDE editor does a better job on code like this.)

在我的经验中,使用编译器的错误诊断信息来知道变量被推导出的类型是相对可靠的方法,利用修订之后的函数模板f来实例化只是声明的模板TD,修订之后的f看起来像下面这样

template<typename T>
void f(const T& param)
{
TD<T> TType; // 引起错误的信息包括了
TD<decltype(param)> paramType; //T和param的类型
}

GNU,Clang和Microsoft的编译器都提供了带有T和param正确类型的错误信息,当时显示的格式各有不同,例如在GUN中(格式经过了一点轻微的修改)

error: 'TD<const Widget *> TType' has incomplete type
error: 'TD<const Widget * const &> paramType' has incomplete

type

除了typeid

如果你想要在运行时获得更正确的推导类型是什么,我们已经知道typeid并不是一个可靠的方法,一个可行的方法是自己实现一套机制来完成从一个类型到它的表示的映射,概念上这并不困难,你只需要利用type trait和模板元编程的方法来将一个完整类型拆分开(使用std::is_const,std::is_ponter,std::is_lvalue_reference之类的type trait),你还需要自己完成类型的每一部分的字符串表示(尽管你依旧需要typeid和std::type_info::name来产生用户自定义格式的字符串表达)

如果你经常需要使用这个方法,并且认为花费在调试,文档,维护上的努力是值得的,那么这是一个合理的方法(If you’d use such a facility often enough to justify the effort needed to write, debug,document, and maintain it, that’s a reasonable approach),但是如果你更喜欢那些移植性不是很强的但是能轻易实现并且提供的结果比typeid更好的代码的, 你需要注意到很多编译器都提供了语言的扩展来产生一个函数签名的字符串表达,包括从模板中实例化的函数,模板和模板参数的类型。

例如,GNU和Clang都支持_PRETTY_FUNCTION_,Microsoft支持了_FUNCSIG_,他们代表了一个变量(在 GNU和Clang中)或是一个宏(在Microsoft中),如果我们将模板f这么实现的话

template<typename T>
void f(const T& param)
{ #if defined(__GNUC__) //对于GNU和
std::cout << __PRETTY_FUNCTION__ << '\n'; // Clang
#elif defined(_MSC_VER)
std::cout << __FUNCSIG__ << '\n'; //对于Microsoft
#endif

}

像之前那样调用f

std::vector<Widget> createVec(); // 工厂函数
const auto vw = createVec(); // 用工厂函数来实例化vw
if (!vw.empty()) {
f(&vw[]); //调用f
}

在GNU中我们得到了以下的结果

void f(const T&) [with T = const Widget*]

告诉我们T的类型被推导为const Widget*(和我们用typeid得到的结果一样,但是前面没有PK的编码和类名前面的6),同时它也告诉我们f参数类型是const T&,如果我们按照这个格式扩展T,我们得到f的类型是const Widget * const&,和typeid的答案不同,但是和使用未定义的模板,产生的错误诊断信息中的类型信息一致,所以它是正确的。

Microsoft的 _FUNCSIG_提供了以下的输出:

void __cdecl f<const classWidget*>(const class Widget *const &)

尖括号里的类型是T被推导的类型,为const Widget*,同样和我们用typeid得到的结果一样,括号内的类型是函数参数的类型,是const Widget* const&,和我们用typeid得到的结果不一样,

但同样和我们使用TD在编译期得到的类型信息一致。

Clang的_PRETTY_FUNCTION_,尽管使用了和GNU一样的名字,但是格式却和GNU或是Microsoft的不一样,它仅仅显示了:

void f(const Widget *const &)

它直接显示出了参数的类型,但是需要我们自己去推导出T的类型被推导为了const Widget*(或者我们也可以利用typeid的信息来获得T的类型)

IDE编辑器,编译器的错误诊断信息,typeid和_PRETTY_FUNCTION_,_FUNCSIG_之类的语言扩展仅仅只是帮助你弄明白编译器推导出的结果是什么,但是最后,没有什么能替代条款1-3中所描述的类型推导相关的指导方针。

请记住:

  • 为了知道推导出类型,你可以使用IDE编辑器,编译器的错误诊断信息,typeid和_PRETTU_FUNCTION_,_FUNCSIG_之类的语言扩展。
  • 这些结果可能既不是十分有用也不是那么精确,所以明白C++的类型推导规则依旧很必要。

Effective Modern C++翻译(5)-条款4:了解如何观察推导出的类型的更多相关文章

  1. Effective Modern C++翻译(7)-条款6:当auto推导出意外的类型时,使用显式的类型初始化语义

    条款6:当auto推导出意外的类型时,使用显式的类型初始化语义 条款5解释了使用auto来声明变量比使用精确的类型声明多了了很多的技术优势,但有的时候,当你想要zag的时候,auto可能会推导出了zi ...

  2. Effective Modern C++翻译(4)-条款3:了解decltype

    条款3 了解decltype decltype是一个有趣的东西,给它一个变量名或是一个表达式,decltype会告诉你这个变量名或是这个表达式的类型,通常,告诉你的结果和你预测的是一样的,但是偶尔的结 ...

  3. Effective Modern C++翻译(3)-条款2:明白auto类型推导

    条款2 明白auto类型推导 如果你已经读完了条款1中有关模板类型推导的内容,那么你几乎已经知道了所有关于auto类型推导的事情,因为除了一个古怪的例外,auto的类型推导规则和模板的类型推导规则是一 ...

  4. Effective Modern C++翻译(2)-条款1:明白模板类型推导

    第一章 类型推导 C++98有一套单一的类型推导的规则:用来推导函数模板,C++11轻微的修改了这些规则并且增加了两个,一个用于auto,一个用于decltype,接着C++14扩展了auto和dec ...

  5. 《Effective Modern C++》翻译--条款4:了解怎样查看推导出的类型

    条款4:了解怎样查看推导出的类型 那些想要了解编译器怎样推导出的类型的人通常分为两个阵营. 第一种阵营是实用主义者.他们的动力通常来自于编敲代码过程中(比如他们还在调试解决中),他们利用编译器进行寻找 ...

  6. Effective Modern C++翻译(6)-条款5:auto比显示的类型声明要更好

        在概念上说,auto关键字和它看起来一样简单,但是事实上,它要更微妙一些的.使用auto会让你在声明变量时省略掉类型,同时也会防止了手动类型声明带来的正确性和性能上的困扰:虽然按照语言预先定义 ...

  7. Effective Modern C++翻译(1):序言

    /*********************************************************** 关于书: 书是我从网上找到的effective Modern C++的样章,内 ...

  8. Effective Modern C++:01类型推导

    C++的官方钦定版本,都是以ISO标准被接受的年份命名,分别是C++98,C++03,C++11,C++14,C++17,C++20等.C++11及其后续版本统称为Modern C++. C++11之 ...

  9. 《Effective Modern C++》翻译--条款2: 理解auto自己主动类型推导

    条款2: 理解auto自己主动类型推导 假设你已经读过条款1关于模板类型推导的内容,那么你差点儿已经知道了关于auto类型推导的所有. 至于为什么auto类型推导就是模板类型推导仅仅有一个地方感到好奇 ...

随机推荐

  1. [2016北京集训试题6]mushroom-[bitset]

    Description Solution bitset是个好东西啊..强行压位什么的真是够orz. 由于所有的蘑菇上房间的长相是一样的,我们针对每个房间,算出它到根节点的bitset和以它为根的子树的 ...

  2. 【转载】ATL问题集

    原文:http://blog.csdn.net/fengrx/article/details/4171629 这些问题是以前在csdn当版主是一些朋友整理的,今天找到了,贴到这里来! #1 如何使用控 ...

  3. spark-client 一直 accepted,无法提交任务,报错Failed to connect to driver at

    这个问题的原因有几个: 1.客户端安装的机器一般是虚拟机,虚拟机的名称可能是随便搞的,然而,yarn-client模式提交任务,是默认把本机当成driver的.所以导致其他的机器无法通过host的na ...

  4. 使用终端命令行将本地项目上传到Github

    使用终端命令行将本地项目上传到Github 转自https://blog.csdn.net/fishball1/article/details/52020305 对于IOS开发者来说,Github的使 ...

  5. idea 开发javaee 时,出现访问的文件和源文件不一样,没有正常更新的解决方案

    这是因为我配置的idea debug 运行模式 输出的文件在 out 和 target 目录下,因为idea本身的原因,导致这两个目录没有及时更新, 导致前端在访问时的页面源码和ide中的一直不一样, ...

  6. C#时间间隔

    System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); stopwatch.Start(); stop ...

  7. 程序员 vs HR(皮这么一下很开心)

    最近网络上一段 HR VS 程序员 的表情包火了 来来来 我们近距离感受下 来源|网络:http://t.cn/RuTKC8B 哈哈哈!大家可以留言说说你们面试时候的趣事 更多内容关注公众号:51re ...

  8. c# table 怎么在前台循环展示 ViewBag

    后台 public ActionResult DoctorEvaluation()//前台页面 { HE_Department HE_dt = new HE_Department(); DataTab ...

  9. BLACKPYTHON学习(一)

    C/S结构了解 所谓的C/S就是客户端(client)和服务器端(server)的简称,也就是在基于这个的基础上编写相关的代码:一个就是客户端一个就是服务端. TCP(client) 客户端编写 #因 ...

  10. 如何把word ppt 思维导图这类文件转化为高清晰度的图片(要干货只看粗体黑字)

    我使用思维导图做学习笔记,最终绘制了一张比较满意的思维导图,想要分享出去,但由于现在思维导图软件众多,成品文件格式差别蛮大,不利于传播和打开,所以需要转化为普通图片,但笔者使用的导图软件导出转化成的图 ...