C++11之后的decltype类型指示符详解

目录
  • 一、什么是decltype类型指示符
  • 二、typeid运算符
  • 三、使用decltype指示符
  • 四、decltype和引用
  • 五、decltype(auto)
  • 六、本章代码汇总

一、什么是decltype类型指示符

有时会遇到这种情况:希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量。为了满足这一要求,C++11 新标准引入了另一种类型说明符 decltype ,它的作用是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却并不实际计算表达式的值。

decltype(f()) sum = x;  // sum 的类型就是函数f的返回类型

编译器并不实际调用函数f,而是使用当调用发生时f的返回值类型作为sum的类型。换句话说,编译器为sum指定的类型是什么呢?就是假如f被调用的话将会返回的那个类型。

二、typeid运算符

C++标准提供了一个typeid运算符来获取与目标操作数类型有关的信息。获取的类型信息会包含在一个类型为std::type_info的对象里。我们可以调用成员函数name获取其类型名,例如:

#include <iostream>

using namespace std;

template<class T1, class T2>
auto sum(T1 t1, T2 t2) -> decltype(t1 + t2)
{
    return t1 + t2;
}

int main()
{
    auto s1 = sum(2, 3);
    cout << "sum(2, 3)=" << s1 << endl;
    cout << "s1 type: " << typeid(s1).name() << endl;

    auto s2 = sum(2.0, 3.0);
    cout << "sum(2.0, 3.0)=" << s2 << endl;
    cout << "s2 type: " << typeid(s2).name() << endl;

    return 0;
}

值得注意的是,成员函数name返回的类型名在C++标准中并没有明确的规范,所以输出的类型名会因编译器而异。比如,MSVC会输出一个符合程序员阅读习惯的名称,而GCC则会输出一个它自定义的名称。

另外,还有3点也需要注意。

  1. typeid的返回值是一个左值,且其生命周期一直被扩展到程序生命周期结束。
  2. typeid返回的std::type_info删除了复制构造函数,若想

保存std::type_info,只能获取其引用或者指针,例如:

auto t1 = typeid(int); // 编译失败,没有复制构造函数无法编译
auto &t2 = typeid(int); // 编译成功,t2推导为const std::type_info&
auto t3 = &typeid(int); // 编译成功,t3推导为const std::type_info*

3.typeid的返回值总是忽略类型的 cv 限定符,也就是

typeid(const T)== typeid(T))

gcc的扩展中还提供了一个名为typeof的运算符,它可以在编译期就获取操作数的具体类型,但typeof并不是C++ 标准。typeid可以获取类型信息并帮助我们判断类型之间的关系,但遗憾的是,它并不能像typeof那样在编译期就确定对象类型。

三、使用decltype指示符

常规用法如下:

int x1 = 0;
decltype(x1) x2 = 0;
std::cout << typeid(x2).name() << std::endl; // x2的类型为int

double x3 = 0;
decltype(x1 + x3) x4 = x1 + x3;
std::cout << typeid(x4).name() << std::endl; // x1+x3的类型为double

decltype({1, 2}) x5; // 编译失败,{1, 2}不是表达式

形参列表中也可以使用:

int x1 = 0;
decltype(x1) sum(decltype(x1) a1, decltype(a1) a2)
{
    return a1 + a2;
}
auto x2 = sum(5, 10);

decltype在尾置返回类型时有着很大用处,例如:

template<class T1, class T2>
auto sum(T1 t1, T2 t2) -> decltype(t1 + t2)
{
    return t1 + t2;
}

需要说明的是,上述用法只推荐在C++11标准的编译环境中使用,因为C++14标准已经支持对auto声明的返回类型进行推导了,所以以上代码可以简化为:

#include <iostream>

using namespace std;

template<class T1, class T2>
auto sum(T1 t1, T2 t2)
{
    return t1 + t2;
}

int main()
{
    auto res = sum(1, 2.0);
    cout << "res=" << res << endl;
    cout << "res type: " << typeid(res).name() << endl;

    return 0;
}

那既然在C++14 标准中decltype的作用又被auto代替了,是否从C++14标准以后decltype就没有用武之地了呢?并不是这样的,auto作为返回类型的占位符还存在一些问题,请看下面的例子:

template<class T>
auto return_ref(T& t)
{
    return t;
}

int x = 0;
cout << "x is reference value: " << std::is_reference_v<decltype(return_ref(x))> << endl;

在上面的代码中,我们期望return_ref返回的是一个T的引用类型,但是如果编译此段代码,会发现auto被推导为值类型。如果想正确地返回引用类型,则需要用到decltype说明符,例如:

template<class T>
auto return_ref(T& t) -> decltype(t)
{
    return t;
}

以上两段代码几乎相同,只是在return_ref函数的尾部用decltype(t)声明了返回类型。

当然了,还有一种方法也可以,使用 auto& :

template<class T>
auto& return_ref1(T& t)
{
    return t;
}

四、decltype和引用

如果 decltype 使用的表达式不是一个变量,则 decltype 返回表达式结果对应的类型。有些表达式将向 decltype 返回一个引用类型。一般当这种情况发生时,意味着该表达式的结果对象能作为一条赋值语句的左值:

int i = 42, *p = &i, &r = i;
decltype(r + 0) b;  // 正确,加法的结果是int,因此b是一个(未初始化的)int
decltype(*p) c;     // 错误,c是int&, 必须初始化

如果表达式的内容是解引用操作,则decltype将得到引用类型,所以decltype(*p)的结果类型是int&,而非int。

decltype 和 auto 的重要区别是,decltype 的结果类型与表达式形式密切相关。有一种情况需要特别注意:对于 decltype 来说,如果变量名加上了一对括号,则得到的类型与不加括号时会有所不同。不加括号的话,得到的结果就是该变量的类型。如果给变量加上了一层或多层括号,编译器就会把它当成是一个表达式。变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型:

decltype((i)) d;    // 错误,d是int&,必须初始化
decltype(i) e;      // 正确,e是一个未初始化的int

总结就是:

  • decltype((val)) 的结果永远是引用。
  • decltype(val) 的结果,只有当val本身就是一个引用时,才是引用

五、decltype(auto)

在 C++14 标准中出现了 decltype 和 auto 两个关键字的结合体:decltype(auto)。它的作用简单来说,就是告诉编译器用decltype的推导表达式规则来推导auto。另外需要注意的是,decltype(auto)必须单独声明,也就是它不能结合指针、引用以及cv限定符。

int i;
int&& f();
auto x1a = i; // x1a推导类型为int
decltype(auto) x1d = i; // x1d推导类型为int
auto x2a = (i); // x2a推导类型为int
decltype(auto) x2d = (i); // x2d推导类型为int&
auto x3a = f(); // x3a推导类型为int
decltype(auto) x3d = f(); // x3d推导类型为int&&
auto x4a = { 1, 2 }; // x4a推导类型为
std::initializer_list<int>
decltype(auto) x4d = { 1, 2 }; // 编译失败, {1, 2}不是表达式
auto *x5a = &i; // x5a推导类型为int*
decltype(auto)*x5d = &i; // 编译失败,decltype(auto)必须单独声明

有了decltype(auto)之后,我们又多了一种返回引用的形式:

template<class T>
decltype(auto) return_ref(T& t)
{
    return t;
}

在C++17 标准中,decltype(auto)还能作为非类型模板形参的占位符,例如:

#include <iostream>
template<decltype(auto) N>
void f()
{
    std::cout << N << std::endl;
}

六、本章代码汇总

#include <iostream>

using namespace std;

template<class T1, class T2>
auto sum(T1 t1, T2 t2)
{
    return t1 + t2;
}

template<class T>
auto return_ref(T& t)
{
    return t;
}

template<class T>
auto& return_ref1(T& t)
{
    return t;
}

template<class T>
auto return_ref2(T& t) -> decltype(t)
{
    return t;
}

template<class T>
decltype(auto) return_ref3(T& t)
{
    return t;
}

template<decltype(auto) N>
void f()
{
    cout << N << endl;
}

int main()
{
    auto res = sum(1, 2.0);
    cout << "res=" << res << endl;
    cout << "res type: " << typeid(res).name() << endl;

    int x = 0;
    cout << "x is reference value: " << std::is_reference_v<decltype(return_ref(x))> << endl;
    cout << "x is reference value: " << std::is_reference_v<decltype(return_ref1(x))> << endl;
    cout << "x is reference value: " << std::is_reference_v<decltype(return_ref2(x))> << endl;
    cout << "x is reference value: " << std::is_reference_v<decltype(return_ref3(x))> << endl;

    return 0;
}

到此这篇关于C++11之后的decltype类型指示符的文章就介绍到这了,更多相关C++11 decltype类型指示符内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++11 中的override详解

    目录 1 公有继承 1.1 纯虚函数 (pure virtual) 1.2 普通虚函数 1.2.1 方法一 1.2.2 方法二 1.3 非虚函数 2 重写 (override) 小结: 参考资料 1 公有继承 公有继承包含两部分:一是"函数接口" (interface),二是"函数实现" (implementation) 如 Shape 类中,三个成员函数,对应三种继承方式: class Shape { public: virtual void Draw() con

  • C++11中移动构造函数案例代码

    目录 1. 拷贝构造函数中的深拷贝问题 2. C++移动构造函数(移动语义的具体实现) 1. 拷贝构造函数中的深拷贝问题 在 C++ 98/03 标准中,如果想用其它对象初始化一个同类的新对象,只能借助类中的拷贝构造函数.拷贝构造函数的实现原理很简单,就是为新对象复制一份和其它对象一模一样的数据.需要注意的是,当类中拥有指针类型的成员变量时,拷贝构造函数中需要以深拷贝(而非浅拷贝)的方式复制该指针成员. 举个例子: #include <iostream> using namespace std

  • C++11如何引入的尾置返回类型

    目录 一.什么是尾置返回类型(trailing return type) 二.尾置返回的典型场景 2.1 常规方式如何返回数组指针 2.2 使用尾置返回类型 三.尾置返回类型的应用 四.总结 一.什么是尾置返回类型(trailing return type) 我们先来看一下传统的函数是怎么定义的: int foo() { return 0; } C++11 标准中引入了尾置返回类型后,上述函数也可定义为: auto foo() -> int { return 0; } 其中 auto 是一个占位

  • C++11返回类型后置语法的使用示例

    C++11新标准增加的auto不仅可以自动推断变量类型,还能结合decltype来表示函数的返回值.这些新特性可以让我们写出更简洁.更现代的代码. 在泛型编程中,可能需要通过参数的运算来得到返回值的类型. 我们看一下下面这个例子: #include<iostream> using namespace std; template <typename R,typename T, typename U> R add(T t,U u) { return t+u; } int main()

  • C++11之后的decltype类型指示符详解

    目录 一.什么是decltype类型指示符 二.typeid运算符 三.使用decltype指示符 四.decltype和引用 五.decltype(auto) 六.本章代码汇总 一.什么是decltype类型指示符 有时会遇到这种情况:希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量.为了满足这一要求,C++11 新标准引入了另一种类型说明符 decltype ,它的作用是选择并返回操作数的数据类型.在此过程中,编译器分析表达式并得到它的类型,却并不实际计算表达式的

  • C++11 并发指南之std::mutex详解

    上一篇<C++11 并发指南二(std::thread 详解)>中主要讲到了 std::thread 的一些用法,并给出了两个小例子,本文将介绍 std::mutex 的用法. Mutex 又称互斥量,C++ 11中与 Mutex 相关的类(包括锁类型)和函数都声明在 <mutex> 头文件中,所以如果你需要使用 std::mutex,就必须包含 <mutex> 头文件. <mutex> 头文件介绍 Mutex 系列类(四种) std::mutex,最基本的

  • C++11 智能指针之shared_ptr代码详解

    C++中的智能指针首先出现在"准"标准库boost中. 随着使用的人越来越多,为了让开发人员更方便.更安全的使用动态内存,C++11也引入了智能指针来管理动态对象. 在新标准中,主要提供了shared_ptr.unique_ptr.weak_ptr三种不同类型的智能指针. 接下来的几篇文章,我们就来总结一下这些智能指针的使用. 今天,我们先来看看shared_ptr智能指针. shared_ptr 智能指针 shared_ptr是一个引用计数智能指针,用于共享对象的所有权也就是说它允许

  • c++11 新特性——智能指针使用详解

    c++11添加了新的智能指针,unique_ptr.shared_ptr和weak_ptr,同时也将auto_ptr置为废弃(deprecated). 但是在实际的使用过程中,很多人都会有这样的问题: 不知道三种智能指针的具体使用场景 无脑只使用shared_ptr 认为应该禁用raw pointer(裸指针,即Widget*这种形式),全部使用智能指针 初始化方法 class A { public: A(int size){ this->size = size; } A(){} void Sh

  • C++20中的结构化绑定类型示例详解

    目录 C++20中新增了一个非常有用的特性 结构化绑定概念 结构化绑定类型 数组 Pair 结构体 实现一个可以被结构化绑定的类元组类型 C++20中新增了一个非常有用的特性 结构化绑定(Structured Binding).它可以让我们方便地从一个容器类型中取出元素并绑定到对应的变量中,使得代码更加简洁.易读.接下来,本文将分别介绍结构化绑定的概念.类型以及如何实现一个可以被结构化绑定的类元组类型. 结构化绑定概念 结构化绑定是C++20中的一个语言特性,允许将一个结构体或者其他类似类型的容

  • C++11学习之多线程的支持详解

    目录 C++11中的多线程的支持 1.C++11中的原子类型 1.1 原子类型的接口 1.2简单自旋锁的实现 2.提高并行程度 2.1 memory_order的参数 2.2 release-acquire内存顺序 2.3 release-consume内存顺序 2.4 小结 3.线程局部存储 4.快速退出 C++11中的多线程的支持 千禧年以后,主流的芯片厂商都开始生产多核处理器,所以并行编程越来越重要了.在C++98中根本没有自己的一套多线程编程库,它采用的是C99中的POSIX标准的pth

  • 关于JavaScript和jQuery的类型判断详解

    对于类型的判断,JavaScript用typeof来进行. 栗子: console.log(typeof null); //object console.log(typeof []); //object console.log(typeof {}); //object console.log(typeof new Date()); //object console.log(typeof new Object); //object console.log(typeof function(){});

  • MySQL5.7 JSON类型使用详解

    JSON是一种轻量级的数据交换格式,采用了独立于语言的文本格式,类似XML,但是比XML简单,易读并且易编写.对机器来说易于解析和生成,并且会减少网络带宽的传输. JSON的格式非常简单:名称/键值.之前MySQL版本里面要实现这样的存储,要么用VARCHAR要么用TEXT大文本. MySQL5.7发布后,专门设计了JSON数据类型以及关于这种类型的检索以及其他函数解析. 我们先看看MySQL老版本的JSON存取. 示例表结构: CREATE TABLE json_test( id INT, p

  • Mysql5.7.11绿色版安装教程图文详解

    Mysql5.7.11绿色版安装教程图文详解如下所示: 1.解压mysql-5.7.11压缩包到想要存放的磁盘文件夹中: 2.在文件夹中新建一个data文件夹和新建一个my.ini文件,并配置my.ini文件 my.ini文件内容:关键点为配置Mysql文件夹中的data目录及存储根目录 3.在我的电脑--属性--高级系统设置--环境变量--配置path变量: 变量值为: 4.使用管理员身份打开cmd.exe程序 注意:如果未使用管理员身份打开的cmd.exe,在mysql安装过程中会出现 --

  • Java 获取泛型的类型实例详解

    Java 获取泛型的类型实例详解 Java 泛型实际上有很多缺陷,比如不能直接获取泛型的类型,不能获取带泛型类等. 以下方式是不正确的: ①.获取带泛型的类的类型 Class lstUClazz = List<User>.class ②获取局部变量泛型的类型 List<User> listUser = new ArrayList<User>(); Type genType = listUser.getClass().getClass().getGenericSuperc

随机推荐