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

条款4:了解怎样查看推导出的类型

那些想要了解编译器怎样推导出的类型的人通常分为两个阵营。

第一种阵营是实用主义者。他们的动力通常来自于编敲代码过程中(比如他们还在调试解决中),他们利用编译器进行寻找,并相信这个能帮他们找到问题的根源。另外一种是经验主义者。他们正在探索条款1-3所描写叙述的推导规则。

而且从大量的推导情景中确认他们预測的结果(“对于这段代码,我觉得推导出的类型将会是…”),可是有时候。他们仅仅是想简单的回答假设这样,会怎么样呢之类的问题?他们可能想知道假设我用一个universal reference(见条款26)替代一个左值的常量形參(比如在函数的參数列表中用T&&替代const T&)模板类型推导的结果会改变吗?

无论你属于哪一个阵营(二者都是合理的),你所要使用的工具取决于你想要在软件开发的哪一个阶段知道编译器推导出的结果。我们会阐述3种可行的方法:在编辑代码的时获得推导的类型。在编译时获得推导的类型,在执行时获得推导的类型。

IDE编辑器

当你在IDE中的编辑代码时,在你将鼠标悬停在程序实体(比如变量,參数。函数等等)上的时候。编译器显示他们的类型。

比如,在以下的代码中。

const int theAnswer = 42 ;

auto x = theAnswer;
auto y = &theAnswer;

IDE编辑器非常可能会显示出x的类型是int,y的类型是const int*。

对于这项工作。你的代码不能过于复杂,由于是IDE内部的C++编译器让IDE提供了这一项信息。

假设编译器不能充分理解并解析你的代码,产生类型推导的结果,它就无法给你显示类型推导的结果。

编译器的诊断

一个有效的得知编译器对某一类型推导出的结果方法是让它产生一个编译期的错误。由于错误的报告信息肯定会提到引起错误的类型。

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

template<typename T>                   //declaration only for TD
class TD;                              //TD == "Type Displayer"

试图实例化这个模板会产生一个错误信息,由于没有模板的定义和实例。为了要查看x和y的类型,仅仅须要用它们的类型实例化TD:

TD<decltype(x)> xType                 //elicit errors containing
TD<decltype(y)> yType                 //x‘s and y‘s types;
                                      //see Item 3 for decltype info

我使用这样的形式的变量名:variableNameType。由于:它们趋向于产生足够实用的错误信息。对于上面的代码,当中一个编译器的错误诊断信息例如以下所看到的(我高亮了我们想要的类型推导结果)

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::name会解决问题的。

你觉得我们能够写下以下的代码来知道x和y 的类型:

std::cout << typeid(x).name() << ‘\n‘;     // display types for
std::cout << typeid(y).name() << ‘\n‘;     // x and y

这种方法依赖于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工具,来对这些重整后的名字进行解码)。理解编译器的输出将变得easy起来,Microsoft的编译器提供了更清楚的输出,x的类型是int,y的类型是int const*.

由于对x和y显示的结果是正确的,你可能会觉得问题已经攻克了。可是我们不能草率。想想以下这个更复杂的样例:

template<typename T>             // template function to
void f(const T& param);          // be called
std::vector<Widget> createVec(); // factory function
const auto vw = createVec();     // init vw w/factory return
if (!vw.empty()) {
f(&vw[0]);                       // call f
}

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

使用typeid看起来是非常直接的方法。仅仅是在f中对你想知道的类型加上一些代码:

template<typename T>
void f(const T& param)
{
    using std::cout;
    cout << "T = " << typeid(T).name() << ‘\n‘;         // show T
    cout << "param = " << typeid(param).name() << ‘\n‘; // show param‘s type
...
}

GNU和Clang的执行结果是以下这样:

T = PK6Widget
param = PK6Widget

我们已经知道PK意味着pointer to const,而6代表了类的名字中有多少个字母(Widget),所以这两个编译器告诉了我们T和param的类型都是const Widget*

微软的编译器提供了以下的结果

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的类型显示用…替代了。

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

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

template<typename T>
void f(const T& param)
{
    TD<T> TType;                   // elicit errors containing
    TD<decltype(param)> paramType; // T‘s and param‘s types
    …
}

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__)                          //For GNU and
    std::cout << __PRETTY_FUNCTION__ << ‘\n‘;  // Clang
#elif defined(_MSC_VER)
    std::cout << __FUNCSIG__ << ‘\n‘;          //For Microsoft
#endif
…
}

像之前那样调用f,

std::vector<Widget> createVec();  // factory function 

const auto vw = createVec();      // init vw w/factory return
if (!vw.empty()) {
f(&vw[0]);                        //call 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、PRETTY_FUNCTIONFUNCSIG这样的语言扩展等。查看类型推导。

?一些工具提供的类型推导结果可能既没实用也不准确,所以理解C++类型推导的原则十分必要。

==============================================================

译者凝视:

IDE 即Integrated Development Environment。是“集成开发环境”的英文缩写,能够辅助开发程序的应用软件。

时间: 06-17

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

《Effective Modern C++》翻译--条款4:了解如何查看推导出的类型

条款4:了解如何查看推导出的类型 那些想要了解编译器如何推导出的类型的人通常分为两个阵营.第一种阵营是实用主义者.他们的动力通常来自于编写程序过程中(例如他们还在调试解决中),他们利用编译器进行寻找,并相信这个能帮他们找到问题的根源.第二种是经验主义者,他们正在探索条款1-3所描述的推导规则.并且从大量的推导情景中确认他们预测的结果("对于这段代码,我认为推导出的类型将会是-"),但是有时候,他们只是想简单的回答如果这样,会怎么样呢之类的问题?他们可能想知道如果我用一个universa

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所描述的推导规则,并且从大量的推导情

Effective Modern C++翻译(2)-条款1

第一章 类型推导 C++98有一套单一的类型推导的规则:用来推导函数模板,C++11轻微的修改了这些规则并且增加了两个,一个用于auto,一个用于decltype,接着C++14扩展了auto和decltype可以使用的语境,类型推导的普遍应用将程序员从必须拼写那些显然的,多余的类型的暴政中解放了出来,它使得C++开发的软件更有弹性,因为在某处改变一个类型会自动的通过类型推导传播到其他的地方.   然而,它可能使产生的代码更难观察,因为编译器推导出的类型可能不像我们想的那样显而易见.   想要在

Effective Modern C++翻译(3)-条款2

条款2 明白auto类型推导 如果你已经读完了条款1中有关模板类型推导的内容,那么你几乎已经知道了所有关于auto类型推导的事情,因为除了一个古怪的例外,auto的类型推导规则和模板的类型推导规则是一样的,但是为什么会这样呢?模板的类型推导涉及了模板,函数和参数,但是auto的类型推导却没有涉及其中的任何一个. 这确实是对的,但这无关紧要,在auto类型推导和template之间存在一个直接的映射,可以逐字逐句的将一个转化为另外一个. 在条款1中,模板类型推导是以下面的模板形式进行举例讲解的:

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

条款6:当auto推导出意外的类型时,使用显式的类型初始化语义 条款5解释了使用auto来声明变量比使用精确的类型声明多了了很多的技术优势,但有的时候,当你想要zag的时候,auto可能会推导出了zig.例如,我有一个函数,它以const Widget&作为参数,并且返回std::vector<bool>,每一个bool暗示了Widget是否提供了一个特殊的特性. std::vector<bool> features(const Widget& w); 进一步假设第

Effective Modern C++翻译(4)-条款3

条款3 了解decltype decltype是一个有趣的东西,给它一个变量名或是一个表达式,decltype会告诉你这个变量名或是这个表达式的类型,通常,告诉你的结果和你预测的是一样的,但是偶尔的结果也会让你挠头思考,开始找一些参考资料进行研究,或是在网上寻找答案.   我们从典型的例子开始,因为它的结果都是在我们预料之中的,和模板类型推导与auto类型推导相比(参见条款1和条款2),decltype几乎总是总是返回变量名或是表达式的类型而不会进行任何的修改 const int i = 0;

《Effective Modern C++》要点中英文对照

目录 CHAPTER 1 Deducing Types 章节1 类型推导 Item 1:Understand template type deduction. 条款1:理解模板类型推导. Item 2:Understand auto type deduction. 条款2:理解auto类型推导. Item 3:Understand decltype. 条款3:理解decltype. Item 4:Know how to view deduced types. 条款4:知道如何查看推导出来的类型.

《Effective Modern C++》翻译--条款1: 理解模板类型推导

北京2016年1月9日13:47:17 开始第一章的翻译. 第一章名为 类型推断 分为四个条款: 1理解模板类型推导 2理解auto自动类型推导 3理解decltype操作符 4如何对待推导的类型 第一章 类型推导 C++98有一套单一的类型推导的规则用来推导函数模板.C++11轻微的修改了这些规则并且增加了两个推导规则,一个用于auto,一个用于decltype.接着C++14扩展了auto和decltype可以使用的语境.类型推导的普遍应用将程序员从必须拼写那些显然多余的类型中解放了出来,它

《Effective Modern C++》翻译--条款3: 理解decltype

条款3:理解decltype decltype 是一个非常有趣的怪兽.如果提供了一个名字或是表达式,decltype关键字将会告诉你这个名字或这个表达式的类型.通常情况下,结果与你的期望吻合.然而有些时候,decltype产生的结果领你挠头,使你去翻阅参考书或在网上问答中寻求答案. 我们先从通常的情况开始-这里没有暗藏惊喜.联系到在模板类型推导和auto类型推导中发生了什么,decltype关键字就像鹦鹉学舌一样,对于变量名称或表达式类型的推导跟模板类型推导和auto类型推导没有任何变化: co