Effective Modern C++翻译(5)-条款4:了解怎么观察推导出的花色

条目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<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[0]); // 调用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[0]); //调用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++的档次推导规则依旧很必要。