【Modern C++ Part3】Understand-decltype

条款三:理解decltype

decltype是一个怪异的发明。给定一个变量名或者表达式,decltype会告诉你这个变量名或表达式的类型。decltype的返回的类型往往也是你期望的。然而有时候,它提供的结果会使开发者极度抓狂而不得参考其他文献或者在线的Q&A网站。

我们从在典型的情况开始讨论,这种情况下decltype不会有令人惊讶的行为。与templatesauto在类型推导中行为相比(请见条款一和条款二),decltype一般只是复述一遍你所给他的变量名或者表达式的类型,如下:

   const int i = 0;            // decltype(i) is const intbool f(const Widget& w);    // decltype(w) is const Widget&// decltype(f) is bool(const Widget&)struct Point{int x, y;                 // decltype(Point::x) is int};Widget w;                   // decltype(w) is Widgetif (f(w)) ...               // decltype(f(w)) is booltemplate<typename T>        // simplified version of std::vectorclass vector {public:...T& operator[](std::size_t index);...};vector<int> v;              // decltype(v) is vector<int>...if(v[0] == 0)               // decltype(v[0]) is int&

看到没有?毫无令人惊讶的地方。

在C++11中,decltype最主要的用处可能就是用来声明一个函数模板,在这个函数模板中返回值的类型取决于参数的类型。举个例子,假设我们想写一个函数,这个函数中接受一个支持方括号索引(也就是"[]")的容器作为参数,验证用户的合法性后返回索引结果。这个函数的返回值类型应该和索引操作的返回值类型是一样的。

操作子[]作用在一个对象类型为T的容器上得到的返回值类型为T&。对std::deque一般是成立的,例如,对std::vector,这个几乎是处处成立的。然而,对std::vector<bool>[]操作子不是返回bool&,而是返回一个全新的对象。发生这种情况的原理将在条款六中讨论,对于此处重要的是容器的[]操作返回的类型是取决于容器的。

decltype使得这种情况很容易来表达。下面是一个模板程序的部分,展示了如何使用decltype来求返回值类型。这个模板需要改进一下,但是我们先推迟一下:

    template<typename Container, typename Index>    // works, butauto authAndAccess(Container& c, Index i)       // requires-> decltype(c[i])                             // refinements{authenticateUser();return c[i];}

auto用在函数名之前和类型推导是没有关系的。更精确地讲,此处使用了C++11的尾随返回类型技术,即函数的返回值类型在函数参数之后声明(“->”后边)。尾随返回类型的一个优势是在定义返回值类型的时候使用函数参数。例如在函数authAndAccess中,我们使用了ci定义返回值类型。在传统的方式下,我们在函数名前面声明返回值类型,ci是得不到的,因为此时ci还没被声明。

使用这种类型的声明,authAndAccess的返回值就是[]操作子的返回值,这正是我们所期望的。

C++11允许单语句的lambda表达式的返回类型被推导,在C++14中之中行为被拓展到包括多语句的所有的lambda·表达式和函数。在上面authAndAccess中,意味着在C++14中我们可以忽略尾随返回类型,仅仅保留开头的auto`。使用这种形式的声明,
意味着将会使用类型推导。特别注意的是,编译器将从函数的实现来推导这个函数的返回类型:

    template<typename Container, typename Index>         // C++14;auto authAndAccess(Container &c, Index i)            // not quite{                                                    // correctauthenticateUser();return c[i];}                                 // return type deduced from c[i]

条款二解释说,对使用auto来表明函数返回类型的情况,编译器使用模板类型推导。但是这样是回产生问题的。正如我们所讨论的,对绝大部分对象类型为T的容器,[]操作子返回的类型是&T, 然而条款一提到,在模板类型推导的过程中,初始表达式的引用会被忽略。思考这对下面代码意味着什么:

    std::deque<int> d;...authAndAccess(d, 5) = 10;       // authenticate user, return d[5],// then assign 10 to it;// this won't compile!

此处,d[5]返回的是int&,但是authAndAccessauto返回类型声明将会剥离这个引用,从而得到的返回类型是intint作为一个右值成为真正的函数返回类型。上面的代码尝试给一个右值int赋值为10。这种行为是在C++中被禁止的,所以代码无法编译通过。

为了让authAndAccess按照我们的预期工作,我们需要为它的返回值使用decltype类型推导,即指定authAndAccess要返回的类型正是表达式c[i]的返回类型。C++的拥护者们预期到在某种情况下有使用decltype类型推导规则的需求,并将这个功能在C++14中通过decltype(auto)实现。这使这对原本的冤家(decltypeauto)在一起完美地发挥作用:auto指定需要推导的类型,decltype表明在推导的过程中使用decltype推导规则。因此,我们可以重写authAndAccess如下:

    template<typename Container, typename Index>  // C++14; works,decltype(auto)                                // but stillauthAndAccess(Container &c, Index i)          // requires{                                             // refinementauthenticateUser();return c[i];}

现在authAndAccess的返回类型就是c[i]的返回类型。在一般情况下,c[i]返回T&authAndAccess就返回T&,在不常见的情况下,c[i]返回一个对象,authAndAccess也返回一个对象。

decltype(auto)并不仅限使用在函数返回值类型上。当时想对一个表达式使用decltype的推导规则时,它也可以很方便的来声明一个变量:

    Widget w;const Widget& cw = w;auto myWidget1 = cw;                     // auto type deduction// myWidget1's type is Widget decltype(auto) myWidget2 = cw            // decltype type deduction:// myWidget2's type is//  const Widget&

我知道,到目前为止会有两个问题困扰着你。一个是我们前面提到的,对authAndAccess的改进。我们在这里讨论。

再次看一下C++14版本的authAndAccess的声明:

    template<typename Container, typename Index>decltype(auto) anthAndAccess(Container &c, Index i);

这个容器是通过非const左值引用传入的,因为通过返回一个容器元素的引用是来修改容器是被允许的。但是这也意味着不可能将右值传入这个函数。右值不能和一个左值引用绑定(除非是const的左值引用,这不是这里的情况)。

诚然,传递一个右值容器给authAndAccess是一种极端情况。一个右值容器作为一个临时对象,在 anthAndAccess 所在语句的最后被销毁,意味着对容器中一个元素的引用(这个引用通常是authAndAccess返回的)在创建它的语句结束的地方将被悬空。然而,这对于传给authAndAccess一个临时对象是有意义的。一个用户可能仅仅想拷贝一个临时容器中的一个元素,例如:

    std::deque<std::string> makeStringDeque(); // factory function// make copy of 5th element of deque returned// from makeStringDequeauto s = authAndAccess(makeStringDeque(), 5);

支持这样的应用意味着我们需要修改authAndAccess的声明来可以接受左值和右值。重载可以解决这个问题(一个重载负责左值引用参数,另外一个负责右值引用参数),但是我们将有两个函数需要维护。避免这种情况的一个方法是使authAndAccess有一个既可以绑定左值又可以绑定右值的引用参数,条款24将说明这正是统一引用(universal reference)所做的。因此authAndAccess可以像如下声明:

    template<typename Container, typename Index>  // c is now adecltype(auto) authAndAccess(Container&& c,   // universalIndex i);        // reference

在这个模板中,我们不知道我们在操作什么类型的容器,这也意味着我们等同地忽略了它用到的索引对象的类型。对于一个不清楚其类型的对象使用传值传递通常会冒一些风险,比如因为不必要的复制而造成的性能降低,对象切片的行为问题,被同事嘲笑,但是对容器索引的情况,正如一些标准库的索引(std::string, std::vector, std::deque[]操作)按值传递看上去是合理的,因此对它们我们仍坚持按值传递。

然而,我们需要更新这个模板的实现,将std::forward应用给统一引用,使得它和条款25中的建议是一致的。

    template<typename Container, typename Index>     // finaldecltype(auto)                                   // C++14authAndAccess(Container&& c, Index i)            // version{authenticateUser();return std::forward<Container>(c)[i];}

这个实现可以做我们期望的任何事情,但是它要求使用支持C++14的编译器。如果你没有一个这样的编译器,你可以使用这个模板的C++11版本。它出了要你自己必须指定返回类型以外,和对应的C++14版本是完全一样的,

    template<typename Container, typename Index>    // finalauto                                            // C++11authAndAccess(Container&& c, Index i)           // version-> decltype(std::forward<Container>(c)[i]){authenticateUser();return std::forward<Container>(c)[i];}

另外一个容易被你挑刺的地方是我在本条款开头的那句话:decltype几乎所有时候都会输出你所期望的类型,但是有时候它的输出也会令你吃惊。诚实的讲,你不太可能遇到这种以外,除非你是一个重型库的实现人员。

为了彻底的理解decltype的行为,你必须使你自己对一些特殊情况比较熟悉。这些特殊情况太晦涩难懂,以至于很少有书会像本书一样讨论,但是同时也可以增加我们对decltype的认识。

对一个变量名使用decltype得到这个变量名的声明类型。变量名属于左值表达式,但这并不影响decltype的行为。然而,对于一个比变量名更复杂的左值表达式,decltype保证返回的类型是左值引用。因此说,如果一个非变量名的类型为T的左值表达式,decltype报告的类型是T&。这很少产生什么影响,因为绝大部分左值表达式的类型有内在的左值引用修饰符。例如,需要返回左值的函数返回的总是左值引用。

这种行为的意义是值得我们注意的。但是在下面这个语句中

    int x = 0;

x是一个变量名,因此decltyper(x)int。但是如果给x加上括号"(x)"就得到一个比变量名复杂的表达式。作为变量名,x是一个左值,同时C++定义表达式(x)也是左值。因此decltype((x))int&。给一个变量名加上括号会改变decltype返回的类型。

C++11中,这仅仅是个好奇的探索,但是和C==14中对decltype(auto)支持相结合,函数中返回语句的一个细小改变会影响对这个函数的推导类型。

    decltype(auto) f1(){int x = 0;...return x;        // decltype(x) is int, so f1 returns int}decltype(auto) f2(){int x = 0;return (x);     // decltype((x)) is int&, so f2 return int&}

f2不仅返回值类型与f1不同,它返回的是对一个局部变量的引用。这种类型的代码将把你带上一个为定义行为的快速列车-你完全不想登上的列车。

最主要的经验教训就是当使用decltype(auto)时要多留心一些。被推导的表达式中看上去无关紧要的细节都可能影响decltype返回的类型。为了保证推导出的类型是你所期望的,请使用条款4中的技术。

同时不能更大视角上的认识。当然,decltype(无论只有decltype或者还是和auto联合使用)有可能偶尔会产生类型推导的惊奇行为,但是这不是常见的情况。一般情况下,decltype会产生你期望的类型。将decltype应用于变量名无非是正确的,因为在这种情况下,decltype做的就是报告这个变量名的声明类型。

要记住的东西
decltype
几乎总是得到一个变量或表达式的类型而不需要任何修改
对于非变量名的类型为T
的左值表达式,decltype
总是返回T&
C++14
支持decltype(auto)
,它的行为就像auto
,从初始化操作来推导类型,但是它推导类型时使用decltype
的规则

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

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

相关文章

前端批量请求场景

文章目录 一、批量请求1、Promise.allSettled2、返回值穿透 二、案例1、 批量任务2、缓存优化3、另一种实现方式 一般时候前端都是简单的查询任务&#xff0c;复杂的数据获取都是后台处理好再返回&#xff0c;如果遇到接口流程化处理、数据组装&#xff0c;可以参考一下。 一、…

芊芊妙音:智能变声,玩转声音魔法

在当今丰富多彩的社交和娱乐环境中&#xff0c;声音的魅力正逐渐被更多人发现和利用。无论是线上社交、短视频创作还是直播互动&#xff0c;一个独特而有趣的声音总能让人眼前一亮&#xff0c;甚至成为个人风格的一部分。《芊芊妙音》正是这样一款能够帮助用户轻松实现声音变换…

安防监控视频汇聚平台EasyCVR v3.7.2版云端录像无法在web端播放的原因排查和解决方法

有用户反馈&#xff0c;在使用EasyCVR视频汇聚平台时&#xff0c;发现云端录像无法在Web页面正常播放。为帮助大家高效解决类似困扰&#xff0c;本文将详细剖析排查思路与解决方案。 用户软件版本信息&#xff1a; 问题排查与解决步骤&#xff1a; 1&#xff09;问题复现验证…

vxe-upload vue 实现附件上传、手动批量上传附件的方式

vxe-upload vue 实现附件上传、手动批量上传附件的方式 查看官网&#xff1a;https://vxeui.com 安装 npm install vxe-pc-ui4.6.47// ... import VxeUIAll from vxe-pc-ui import vxe-pc-ui/lib/style.css // ...createApp(App).use(VxeUIAll).mount(#app) // ...上传附件支…

leaflet【十一】地图瓦片路径可视化

前言 在开发调试过程当中&#xff0c;如果引入的是公司内部的Gis地图信息或者一些第三方定制来的Gis地图数据&#xff0c;当某一些地图块数据缺失的时候&#xff0c;要打开F12去一个个找那些链接&#xff08;去找对应的xy与layer&#xff09;失效、那么你可能需要使用以下插件…

ES6从入门到精通:模块化

ES6 模块化基础概念ES6 模块化是 JavaScript 官方标准&#xff0c;通过 import 和 export 语法实现代码拆分与复用。模块化特点包括&#xff1a;每个文件是一个独立模块&#xff0c;作用域隔离。支持静态分析&#xff0c;依赖关系在编译时确定。输出的是值的引用&#xff08;动…

stm32 USART串口协议与外设——江协教程踩坑经验分享

江协stm32学习&#xff1a;9-1~9-3 USART串口协议与外设 一、串口通信基础知识 1、通信类型&#xff1a; 全双工通信&#xff1a;通信双方能够同时进行双向通信。一般有两根通信线&#xff0c;如USART中的TX&#xff08;发送&#xff09;和RX&#xff08;接收&#xff09;线&am…

深度学习中的一些名词

向前传播 forward pass 在机器学习中&#xff0c;输入的feature, 通过预测模型&#xff0c;输出预测值&#xff0c;此过程称之为向前传播&#xff1b; 向后传播 backward pass 为了将预测与真实值的产值减小&#xff0c;需要根据差值&#xff0c;更新模型中的参数&#xff0c;此…

鸿蒙系统(HarmonyOS)应用开发之手势锁屏密码锁(PatternLock)

项目概述 基于鸿蒙&#xff08;OpenHarmony&#xff09;平台开发的手势密码锁应用&#xff0c;旨在为用户提供安全、便捷且具有良好交互体验的身份验证方式。通过手势图案输入&#xff0c;用户可以轻松设置和验证密码&#xff0c;提升设备的安全性和个性化体验。 功能特点 手…

vue文本插值

好的&#xff0c;我们来详细讲解 Vue 中最基础的数据展示方式&#xff1a;文本插值和在其内部使用的 JavaScript 表达式。 1. 文本插值 (Text Interpolation) 知识点: 文本插值是 Vue 中最基本的数据绑定形式。它使用“Mustache”语法&#xff08;双大括号 {{ }}&#xff09;…

Python:线性代数,向量内积谐音记忆。

目录1 先说结论2 解释3 欢迎纠错4 论文写作/Python 学习智能体------以下关于 Markdown 编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、…

小程序导航设置更多内容的实现方法

在小程序中实现导航栏设置更多内容&#xff0c;可以通过以下几种方式实现&#xff1a; 1. 使用原生导航栏自定义按钮 javascript // app.json 或页面.json中配置 {"navigationBarTitleText": "首页","navigationBarTextStyle": "black&q…

SpringBoot 解决配置文件有黄色波浪线

在application.properties配置文件中有黄色波浪线&#xff0c;是警告!! 原因&#xff1a;编码格式不一致&#xff01;&#xff01; 解决&#xff1a;Settings| Editor | File Encodings | 选择UTF-8

在 Vue 3 中全局使用 Suspense 组件

Suspense 是 Vue 3 引入的一个内置组件&#xff0c;不需要引用可以直接用。用于处理异步依赖的等待状态。虽然 Suspense 主要用于异步组件&#xff0c;但你也可以全局地使用它来管理整个应用的加载状态。 全局 Suspense 的基本用法 1. 在根组件中使用 Suspense // main.js 或…

笔记/计算机网络

Content 计算机网络部分核心概念十大网络协议一览 计算机网络部分核心概念 1. 什么是计算机网络&#xff1f;它最基本的功能是什么&#xff1f; 计算机网络是指通过某种传输介质将多台独立的计算机或设备连接起来&#xff0c;实现数据交换和资源共享的系统。其最基本的功能是数…

时频图数据集更正程序,去除坐标轴白边及调整对应的标签值

当数据集是时频图时可能有一个尴尬的问题&#xff0c;就是数据集制作好后&#xff0c;发现有白边。 其实这也不影响训练模型&#xff0c;可能对模型训练效果的影响也是微乎其微的&#xff0c;于是大多数情况我会选择直接用整张图片训练模型。但是&#xff0c;有的情况下&#x…

mv重命名报错:bash:未预期的符号 ‘(‘附近有语法错误

文章目录 一、报错背景二、解决方法2.1、方法一&#xff1a;文件名加引号2.2、方法二&#xff1a;特殊字符前加\进行转义 一、报错背景 在linux上对一文件执行重命名时报错。原因是该文件名包含空格与括号。 文件名如下&#xff1a; aa &#xff08;1).txt执行命令及报错如下…

Unity-MMORPG内容笔记-其三

继续之前的内容&#xff1a; 战斗系统 无需多言&#xff0c;整个项目中最复杂的部分&#xff0c;也是代码量最大的部分。 属性系统 首先我们要定义一系列属性&#xff0c;毕竟所谓的战斗就是不断地扣血对吧。 属性系统是战斗系统的核心模块&#xff0c;负责管理角色的所有…

Linux入门篇学习——Linux 帮助手册

目录 一、Linux 帮助手册 1.怎么打开帮助手册 2.安装依赖 3.使用手册查看命令 一、Linux 帮助手册 1.怎么打开帮助手册 打开 ubuntu &#xff0c;输入 man 命令打开帮助手册&#xff0c;直接在控制台输入 man 就可以了&#xff0c; man 手册一共有 9 页&#xff0c…

2025年后端主流框架对比和竞争格局及趋势发展

2025年的后端开发呈现出云原生主导、性能革命、AI深度融合的技术格局&#xff0c;主流框架在细分领域持续分化&#xff0c;新兴技术快速渗透关键场景。以下是基于行业实践与技术演进的深度解析&#xff1a; 一、主流框架竞争态势与核心能力 1. Java生态&#xff1a;企业级市场的…