《Effective Modern C++》学习笔记 - Item 4: 了解如何查看推导的类型

(笔者注:本篇的技巧,尤其是关于 typeid 和boost库用法的讲解非常实用)

  • 分三种情形讨论如何获得类型推导的结果:编辑代码时获得,编译时获得和运行时获得。

IDE编辑器

  • 多数现代IDE都有这类功能,当鼠标悬浮在实体上时,显示其类型。
  • 这也说明IDE中通常带有能感知代码信息的编译器,或至少是编译器的前端。
  • 对于简单的类型(如 int),IDE提供的信息很有用。但后面我们会看到,当涉及的类型变复杂后,IDE展示的信息可能并不是非常有帮助。

编译器诊断信息(Diagnostics)

  • 展示推断类型的一种有效方法就是让编译器报与类型相关的错(笔者:读到这儿着实笑了ww)。例如,声明但不定义一个模板类,想查看任何变量的类型,将其传给模板参数即可:
template<typename T>
class TD;

// x和y的类型会显示在编译错误信息中
TD<decltype(x)> xType;
TD<decltype(y)> yType;

在这里插入图片描述

运行时输出

  • 问题在于如何创造一个类型的字符串表示(笔者注:大一时这一直是个困扰我的问题,网上学的typeid也不太好用,现在终于得到了一个答案…不过还是要说类型相关的功能至少在使用上,C++确实不如Java, C#, Python等语言方便)
  • 解决方法1:使用C++标准的 typeid。对任意对象使用 typeid 返回一个 std::type_info 对象,该对象有一个成员函数 name(),返回变量类型的 const char* 字符串。name 函数的定义由实现决定,没有统一标准。 例如,MSVC编译器的结果就和GCC不同:
    在这里插入图片描述

VS2022编译结果。类型的可读性较好。

在这里插入图片描述

VSCode使用g++编译结果。i代表int,PKi代表Pointer K(C)onst to int,可读性略差。这些名称源于编译器对类型的内部编码(name mangling机制),可以用 c++filt 工具解码。

  • 以上设定过于简单。让我们向前一步,看看更接近实际应用场景的例子:
// 模板函数,打印T和param的类型名
template<typename T>
void f(const T& param)
{
    cout << "T =     " << typeid(T).name() << '\n';
    cout << "param = " << typeid(param).name() << '\n';
}
// 某个自定义类
class Widget {};
// 生产Widget数组的工厂函数
vector<Widget> createVec()
{
    return vector<Widget>{ Widget() }; // 假设返回的vector含一个元素
}
int main()
{
    const auto vw = createVec();
    if (!vw.empty()) {
        f(&vw[0]);
    }
    return 0;
}

MSVC和GCC运行结果:
在这里插入图片描述
在这里插入图片描述

PK代表pointer-to-const,6是Widget类名的长度。详见name mangling规则。

  • 问题来了:在模板中,param的类型为 const T&,既然如此,paramT 的类型不应该是不同的吗?例如,Tintparam 应为 const int&。实际上,两种编译器都告诉我们,paramT 的类型均为 const Widget*
  • 遗憾的是,std::type_info::name 的结果并不可靠,因为它将类型以类似模板按值传递(Case 3)的推断规则处理,这导致类型的引用性、constvolatile 都被丢失。实际上,param 的类型应为 const Widget * const &
  • 同样遗憾的是,IDE展示的类型信息可能同样不靠谱,或至少起不到什么帮助。对于此例,有些IDE将 Tparam 的类型报告为:
// 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 &

param 看着没那么吓人,但其中的 ... 实际上是缩略。

  • 笔者注:本例中以上问题在我的环境中没有得到复现。目前(2021.12)VS2022和VSCode都报告了正确的函数模板推导结果(不过,我确实有深刻印象VSCode在使用STL类型时经常提示各种大长串难以理解的类型标识符)。
    在这里插入图片描述
    在这里插入图片描述
  • 主角登场!作者强力推荐 Boost 库中的 Boost.TypeIndex。以下为使用样例:
#include <boost/type_index.hpp> // boost库可在官网https://www.boost.org/下载

template<typename T>
void f(const T& param)
{
	 using std::cout;
	 using boost::typeindex::type_id_with_cvr;
	// show T
	cout << "T = "
		 << type_id_with_cvr<T>().pretty_name()
		 << '\n';
 	// show param's type
	cout << "param = "
		 << type_id_with_cvr<decltype(param)>().pretty_name()
		 << '\n';
}
  • 这种方法使用了 boost::typeindex::type_id_with_cvr 模板,其接受一个类型参数,并且不会抹除 constvolatile和引用修饰符(因此得名 with_cvr)。返回结果是一个 boost::typeindex::type_index 对象,其有一个成员函数 pretty_name,返回一个容易阅读的 std::string 类型名称字符串。MSVC和GCC运行结果:
    在这里插入图片描述
    在这里插入图片描述

总结

  1. 推导出的类型可以通过IDE编辑器,编译错误信息和Boost TypeIndex库查看。
  2. 一些工具的结果可能既不准确也没什么用,因此,我们自身对于C++类型推导规则的理解仍是最重要的。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值