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

news2024/11/27 20:34:27

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;
}

20230109221937

值得注意的是,成员函数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;
}

20230109230235

那既然在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;
}

20230109235259

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/153353.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度对比学习综述

本文综合考察了对比学习近年的发展和进步, 提出一种新的面向对比学习的归类方法, 并基于提出的归类方法, 对现有对比研究成果进行系统综述, 并评述代表性方法的技术特点和区别, 系统对比分析现有对比学习方法在不同基准数据集上的性能表现。 摘要 在深度学习中, 如何利用大量、…

Linux - 目录与文件操作

目录1.操作目录1.1 目录切换1.2 浏览目录1.3 目录创建1.4 目录删除1.5 复制目录1.6 移动或重命名目录2. 操作文件2.1 查找文件2.2 查看文件信息2.3 查看文件内容2.4 创建文件2.5 文件修改-vim2.6 删除文件2.7 复制和重命名文件3. 文件或目录进行压缩或解压3.1 压缩3.2 解压1.操…

链路追踪工具之Zipkin

Zipkin是一个分布式跟踪系统&#xff0c;Zipkin的设计是基于谷歌的Google Dapper论文&#xff0c;它可以帮助收集时间数据&#xff0c;在microservice架构下&#xff0c;通过链路追踪&#xff0c;可以便捷的分析服务调用延迟问题。每个应用程序向Zipkin server端报告数据&#…

【高光谱、多光谱和全色图像融合】

HyperFusion: A Computational Approach for Hyperspectral, Multispectral, and Panchromatic Image Fusion &#xff08;超融合&#xff1a;高光谱、多光谱和全色图像融合的计算方法&#xff09; 高空间分辨率的高光谱图像&#xff08;HSI&#xff09;和多光谱图像&#xff…

链表热门面试题(二)

目录前言一、删除链表的倒数第 N 个结点二、两两交换链表中的节点三、旋转链表四、删除排序链表中的重复元素五、删除排序链表中的重复元素 II六、反转链表II七、删除链表中的节点八、奇偶链表前言 一、删除链表的倒数第 N 个结点 题目&#xff1a; 方法1&#xff1a;找到删除…

【Java多线程】创建多线程方式一

线程的创建和启动 *Java语言的JVM允许程序运行多个线程&#xff0c;它通过java.lang.Thread类来体现。 *Thread类的特性 每个线程都是通过某个特定Thread对象的run()方法来完成操作的&#xff0c;经常 把run()方法的主体称为线程体 通过该Thread对象的start()方法来启动这个…

判断两条线段是否相交

参考链接&#xff1a; 1 2 一、判断线段是否相交需要下面两步&#xff1a; &#xff08;1&#xff09;快速排斥实验 &#xff08;2&#xff09;跨立实验 二、第一步快速排斥实验 对上图两条L1,L2线段来说&#xff0c;L1 x的最大值为d端点x5&#xff0c;L2 x的最小值为a端点x…

RobotFramework环境安装和入门

环境安装1、安装python建议版本3.7&#xff0c;Robot Framework不支持python最新版。 可以通过以下地址下载https://www.python.org/ftp/python/3.7.3/python-3.7.3-amd64.exe安装过程中选择添加到环境变量。2、安装Robot Framework以及所需组件注意各个组件的版本号&#xff0…

LVGL学习笔记15 - 文本框TextArea

目录 1. Parts 2. 样式 2.1 修改背景bg 2.2 修改边界border 2.3 修改文本 2.3.1 lv_textarea_add_char 2.3.2 lv_textarea_add_text 2.3.3 lv_textarea_set_text 2.3.4 设定可输入的字符 2.3.5 删除字符 2.3.6 设定字符串最大长度 2.4 滚动条 2.4.1 滚动方向 2.4.2 …

RHCE学习笔记-133-1

RH133-01 installation 光盘引导后,在提示符下输入:linux askmethod 可以支持:local cdrom,hard drive,nfs image,ftp,http方式 cpu on x86 2个物理超线程CPU 使用smp or hugemem kernel可以支持 32个物理cpus 内存 标准x86 kernel 4G smp i686/athlon kernel 16G hugemem SMP …

创建新分支,基于某一分支创建新分支,git push --set-upstream origin

背景&#xff1a; 基于hcc1.2的开发分支拉去hcc_hotfix_1.2.0分支&#xff0c;解读&#xff1a;在仓库中基于某个分支&#xff0c;创建一个新的分支 做法&#xff08;解决&#xff09;&#xff1a; 1、第一步&#xff1a;git branch hcc_hotfix_1.2.0 2、第二步&#xff1a;g…

葡萄城邀您参与「表格技术开发者新年分享会」,共话表格技术新未来!

数据资产是企业最重要的资产之一&#xff0c;表格也是企业最普遍的数据整理手段。在数字经济时代&#xff0c;各行业飞速发展&#xff0c;拥抱数字化、进行数字化升级已成为大势所趋&#xff0c;企业对协同办公、大数据分析、表格智能化应用等领域的需求也呈爆发式增长&#xf…

GrapeCity Documents Image Viewer JavaScript Crack

GrapeCity Documents Image Viewer 是跨平台 JavaScript 图像查看器 允许用户使用我们的 JavaScript 图像查看器在您的 Web 应用程序中查看和编辑图像。采集 by Ω578867473 支持多种图像格式 适用于所有现代浏览器和框架&#xff0c;包括 Edge、Chrome、Firefox、Opera、Saf…

【UE4 第一人称射击游戏】33-创建一个小地图

上一篇&#xff1a;【UE4 第一人称射击游戏】32-添加击杀AI的提示功能&#xff08;使用到控件蓝图的动画功能&#xff09;本篇效果&#xff1a;可以看到左上角完成了小地图的制作步骤&#xff1a;打开名为“FPSHUD”的控件蓝图&#xff0c;拖入一个图像控件&#xff0c;表示迷你…

Diffusion 和Stable Diffusion的数学和工作原理详细解释

扩散模型的兴起可以被视为人工智能生成艺术领域最近取得突破的主要因素。而稳定扩散模型的发展使得我们可以通过一个文本提示轻松地创建美妙的艺术插图。所以在本文中&#xff0c;我将解释它们是如何工作的。 扩散模型 Diffusion 扩散模型的训练可以分为两部分: 正向扩散→在图…

输出全排列(C++版)

目录 输出全排列 一、问题描述 二、运行环境说明 ​三、代码段 四、效果展示 输出全排列 备注&#xff1a;大二&#xff08;上&#xff09;数据结构课程设计A题 一、问题描述 请编…

我是如何利用cps平台赚钱的?

你好&#xff0c;我是你们熟悉而又陌生的好朋友梦龙&#xff0c;一个创业期的年轻人 今天跟你做个分享&#xff0c;众所周知互联网是一块非常大的蛋糕&#xff0c;几位互联网巨头也做不到完全吃透&#xff0c;这次梦龙给你分享的是实际的经验&#xff0c;实际的案例分享。 最开…

Spring存储和读取对象 -- 2

Spring存储和读取对象 -- 2前言一、存储 Bean 对象1.1 前置工作&#xff1a;配置扫描路径 (重要)1.2 添加注解存储 Bean 对象1.2.1 五大类注解Bean 命名规则1.2.2 方法注解 BeanBean 命名规则与重命名二、获取 Bean 对象 (对象注入)2.1 属性注入优点分析缺点分析2.2 Setter 注入…

如何在Jetbrain Rider中使用EntityFrameWork (Core) 命令行,如添加迁移和更新数据库

在Visual Studio中&#xff0c;EntityFrameWork命令如Add-Migration和Update-Database通常在包管理器控制台中运行。这在Visual Studio中工作得很好&#xff0c;但不幸的是&#xff0c;它不是可移植的。这些命令是基于powershell的&#xff0c;并且包管理器控制台绑定了Visual …

开源的文档型数据库--MongoDB(安装)

1、简介 MongoDB 是一个开源的、文档数据库管理系统。它提供了高性能、高可扩展性和高可用性。 MongoDB 使用了一种称为 BSON 的二进制形式的 JSON 来存储数据。这使得 MongoDB 可以轻松地存储各种数据类型&#xff0c;包括大型对象和二进制文件。 MongoDB 的一个重要特性是它的…