C++类型推导

这里对C++的类型推导方式进行一次全面的总结。

C++中有三种类型推导的方式,分别是模板、auto以及decltype()。以下分别介绍这三种方式的同异。

一 模板

假设有这样的函数模板和这样的调用:

template<typename T>
void f(ParamType param);f(expr);                        //使用表达式调用f

T的类型推导不仅取决于expr的类型,也取决于ParamType的类型。下面份三种情况讨论这个事情。

1 ParamType是一个指针或引用,但不是通用引用

在这种情况下,类型推导会这样进行:

  1. 如果expr的类型是一个引用,忽略引用部分
  2. 然后expr的类型与ParamType进行模式匹配来决定T
    有如下例子:
template<typename T>
void f(T& param);            //param是一个引用int x=27;                    //x是int
const int cx=x;              //cx是const int
const int& rx=x;             //rx是指向作为const int的x的引用//不同的调用中,对param和T推导的类型会是这样
f(x);                        //T是int,param的类型是int&
f(cx);                       //T是const int,param的类型是const int&
f(rx);                       //T是const int,param的类型是const int&

在第二个和第三个调用中,注意因为cxrx被指定为const值,所以T被推导为const int

因为他们传递一个const对象给一个引用类型的形参时,他们期望对象保持不可改变性,也就是说,形参是reference-to-const的。这也是为什么将一个const对象传递给以T&类型为形参的模板安全的:对象的常量性constness会被保留为T的一部分。

在第三个例子中,注意即使rx的类型是一个引用,T也会被推导为一个非引用 ,因为rx的引用性(reference-ness)在类型推导中会被忽略。
如果我们将f的形参类型T&改为const T&,情况有所变化:

template<typename T>
void f(const T& param);         //param现在是reference-to-constint x = 27;                     //如之前一样
const int cx = x;               //如之前一样
const int& rx = x;              //如之前一样f(x);                           //T是int,param的类型是const int&
f(cx);                          //T是int,param的类型是const int&
f(rx);                          //T是int,param的类型是const int&

cxrxconstness依然被遵守,但是因为现在我们假设param是reference-to-constconst不再被推导为T的一部分。
如果param是一个指针(或者指向const的指针)而不是引用,情况本质上也一样:

template<typename T>
void f(T* param);               //param现在是指针int x = 27;                     //同之前一样
const int *px = &x;             //px是指向作为const int的x的指针f(&x);                        //T是int,param的类型是int*
f(px);                        //T是const int,param的类型是const int*

2 ParamType是一个通用引用

其推导规则如下:

  • 如果expr是左值,TParamType都会被推导为左值引用。这非常不寻常,第一,这是模板类型推导中唯一一种T被推导为引用的情况。第二,虽然ParamType被声明为右值引用类型,但是最后推导的结果是左值引用。
  • 如果expr是右值,就使用正常的(也就是情景一)推导规则。
    示例如下:
template<typename T>
void f(T&& param);              //param现在是一个通用引用类型int x=27;                       //如之前一样
const int cx=x;                 //如之前一样
const int & rx=cx;              //如之前一样f(x);                           //x是左值,所以T是int&,//param类型也是int&f(cx);                          //cx是左值,所以T是const int&,//param类型也是const int&f(rx);                          //rx是左值,所以T是const int&,//param类型也是const int&f(27);                          //27是右值,所以T是int,//param类型就是int&&

3 ParamType既不是指针也不是引用

ParamType既不是指针也不是引用时,我们通过传值(pass-by-value)的方式处理,这意味着无论传递什么param都会成为它的一份拷贝——一个完整的新对象。事实上param成为一个新对象这一行为会影响T如何从expr中推导出结果。

  1. 和之前一样,如果expr的类型是一个引用,忽略这个引用部分
  2. 如果忽略expr的引用性(reference-ness)之后,expr是一个const,那就再忽略const。如果它是volatile,也忽略volatile

示例如下:

template<typename T>
void f(T param);                //以传值的方式处理paramint x=27;                       //如之前一样
const int cx=x;                 //如之前一样
const int & rx=cx;              //如之前一样f(x);                           //T和param的类型都是int
f(cx);                          //T和param的类型都是int
f(rx);                          //T和param的类型都是int

注意即使cxrx表示const值,param也不是const。这是有意义的。param是一个完全独立于cxrx的对象——是cxrx的一个拷贝。
但是考虑这样的情况, expr是一个const指针,指向const对象,expr通过传值传递给param

template<typename T>
void f(T param);                //仍然以传值的方式处理paramconst char* const ptr =         //ptr是一个常量指针,指向常量对象"Fun with pointers";f(ptr);                         //传递const char * const类型的实参

在这里,解引用符号 * 的右边的const表示ptr本身是一个constptr不能被修改为指向其它地址,也不能被设置为null(解引用符号左边的const表示ptr指向一个字符串,这个字符串是const,因此字符串不能被修改)。当ptr作为实参传给f,组成这个指针的每一比特都被拷贝进param。像这种情况,ptr自身的值会被传给形参,根据类型推导的第三条规则,ptr自身的常量性constness将会被省略,所以paramconst char*,也就是一个可变指针指向const字符串。在类型推导中,这个指针指向的数据的常量性constness将会被保留,但是当拷贝ptr来创造一个新指针param时,ptr自身的常量性constness将会被忽略。

两个特殊情况

1 数组实参

如果将一个数组传值给一个模板,会发生什么?

template<typename T>
void f(T param);                        //传值形参的模板f(name);                                //T和param会推导成什么类型?

这里有一个函数的形参是数组,但是数组声明会被视作指针声明,这意味着下面的两个声明是等价的:

void myFunc(int param[]);
void myFunc(int* param);                //与上面相同的函数

因为数组形参会视作指针形参,所以传值给模板的一个数组类型会被推导为一个指针类型。这意味着在模板函数f的调用中,它的类型形参T会被推导为const char*

const char name[] = "J. P. Briggs";     //name的类型是const char[13]f(name);                        //name是一个数组,但是T被推导为const char*

但是现在难题来了,虽然函数不能声明形参为真正的数组,但是可以接受指向数组的引用!所以我们修改f为传引用, 然后调用它:

template<typename T>
void f(T& param);                       //传引用形参的模板f(name);                                //传数组给f

T被推导为了真正的数组!这个类型包括了数组的大小,在这个例子中T被推导为const char[13]f的形参(对这个数组的引用)的类型则为const char (&)[13]。是的,这种语法看起来简直有毒,但是知道它将会让你在关心这些问题的人的提问中获得大神的称号。

2 函数实参
在C++中不只是数组会退化为指针,函数类型也会退化为一个函数指针,我们对于数组类型推导的全部讨论都可以应用到函数类型推导和退化为函数指针上来。结果是:

void someFunc(int, double);         //someFunc是一个函数,//类型是void(int, double)template<typename T>
void f1(T param);                   //传值给f1template<typename T>
void f2(T & param);                 //传引用给f2f1(someFunc);                       //param被推导为指向函数的指针,//类型是void(*)(int, double)
f2(someFunc);                       //param被推导为指向函数的引用,//类型是void(&)(int, double)

二 auto

auto是建立在模板类型推导的基础上的,这里举出它们的同异点。

1 相同点

auto类型推导和模板类型推导有一个直接的映射关系,比如以下模板:

template<typename T>
void f(ParmaType param);f(expr);                        //使用一些表达式调用f

当一个变量使用auto进行声明时,auto扮演了模板中T的角色,变量的类型说明符扮演了ParamType的角色。

比如以下示例:

auto x = 27;			//这里的x的类型说明符是auto自己
const auto cx = x;		//这里的x的类型说明符是const auto
const auto & rx=cx;		//这里的x的类型说明符是const auto &

因此Item1描述的三个情景稍作修改就能适用于auto:

  • 情景一:类型说明符是一个指针或引用但不是通用引用
  • 情景二:类型说明符一个通用引用
  • 情景三:类型说明符既不是指针也不是引用

auto类型推导和模板类型推导几乎一样的工作,它们就像一个硬币的两面,除了一个例外。下面来说说这个例外。

2 不同点

auto类型推导和模板类型推导的真正区别在于,auto类型推导假定花括号表示std::initializer_list而模板类型推导不会这样(确切的说是不知道怎么办)。

auto x1 = 27;                   //类型是int,值是27
auto x2(27);                    //同上
auto x3 = { 27 };               //类型是std::initializer_list<int>,值是{ 27 }
auto x4{ 27 };                  //同上template<typename T>            //带有与x的声明等价的
void f(T param);                //形参声明的模板f({ 11, 23, 9 });               //错误!不能推导出T

然而如果在模板中指定Tstd::initializer_list而留下未知T,模板类型推导就能正常工作:

template<typename T>
void f(std::initializer_list<T> initList);f({ 11, 23, 9 });               //T被推导为int,initList的类型为//std::initializer_list<int>

C++14允许auto用于函数返回值并会被推导(参见Item3),而且C++14的lambda函数也允许在形参声明中使用auto。但是在这些情况下auto实际上使用模板类型推导的那一套规则在工作,而不是auto类型推导,所以说下面这样的代码不会通过编译:

auto createInitList()
{return { 1, 2, 3 };         //错误!不能推导{ 1, 2, 3 }的类型
}std::vector<int> v;auto resetV =[&v](const auto& newValue){ v = newValue; };        //C++14resetV({ 1, 2, 3 });            //错误!不能推导{ 1, 2, 3 }的类型

三 decltype

decltype总是不加修改的产生变量或者表达式的类型。

对于T类型的不是单纯的变量名的左值表达式,decltype总是产出T的引用即T&

C++14支持decltype(auto),就像auto一样,推导出类型,但是它使用decltype的规则进行推导。

以下说明decltype的一个重要的用法:

有如下函数:

template<typename Container, typename Index>    //可以工作,
auto authAndAccess(Container& c, Index i)       //但是需要改良
{authenticateUser();return c[i];
}std::deque<int> d;authAndAccess(d, 5) = 10;               //认证用户,返回d[5],//然后把10赋值给它//无法通过编译器!

对一个T类型的容器使用operator[] 通常会返回一个T&对象,比如std::deque就是这样。(但是std::vector有一个例外,对于std::vectoroperator[]不会返回bool&,它会返回一个全新的对象, 这是一个例外)。

函数返回类型中使用auto,编译器实际上是使用的模板类型推导的那套规则。如果那样的话这里就会有一些问题。正如我们之前讨论的,operator[]对于大多数T类型的容器会返回一个T&,但是在模板类型推导期间,表达式的引用性(reference-ness)会被忽略。基于这样的规则,在这里d[5]本该返回一个int&,但是模板类型推导会剥去引用的部分,因此产生了int返回类型。函数返回的那个int是一个右值,上面的代码尝试把10赋值给右值int,C++11禁止这样做,所以代码无法编译。

要想让authAndAccess像我们期待的那样工作,我们需要使用decltype类型推导来推导它的返回值,C++期望在某些情况下当类型被暗示时需要使用decltype类型推导的规则,C++14通过使用decltype(auto)说明符使得这成为可能。

template<typename Container, typename Index>    //最终的C++14版本
decltype(auto)
authAndAccess(Container&& c, Index i)
{authenticateUser();return std::forward<Container>(c)[i];
}

decltype(auto)的使用不仅仅局限于函数返回类型,当你想对初始化表达式使用decltype推导的规则,你也可以使用:

Widget w;const Widget& cw = w;auto myWidget1 = cw;                    //auto类型推导//myWidget1的类型为Widget
decltype(auto) myWidget2 = cw;          //decltype类型推导//myWidget2的类型是const Widget&

总结

以上是C++类型推导的全部内容。充分使用C++的类型推导,能使我们尽可能简单的代码。

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

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

相关文章

【Linux】多线程

文章目录 一.Linux线程概念1.什么是线程2.二级页表3.线程的优点4.线程的缺点5.线程异常6.线程用途 二.Linux进程VS线程1.进程和线程2.进程的多个线程共享3.进程和线程的关系 三.Linux线程控制1.POSIX线程库2.线程创建3.线程等待4.线程终止5.分离线程6.线程ID及进程地址空间布局…

手机拍摄的视频噪点很多怎么办,视频怎么做降噪处理?

现如今&#xff0c;智能手机已经成为了我们生活中必不可少的存在。而随着智能手机越来越强大&#xff0c;很多人已经开始使用手机来拍摄各种类型的视频。但是由于手机的限制&#xff0c;很多人会发现自己拍摄的视频存在着很多的噪点。那么&#xff0c;我们该怎样来解决拍摄视频…

N点复序列求2个N点实序列的快速傅里叶变换

一、方法简介 通过一个点复数序列求出两个点实数序列的离散傅里叶变换&#xff0c;进一步提升快速傅里叶变换的效率。 二、方法详解 和是实数序列&#xff0c;且长度都为&#xff0c;定义复数序列&#xff1a; &#xff0c; 则序列和可表示为&#xff1a; 的离散傅…

端到端的机器学习项目(Machine Learning 研习之六)

使用真实数据 当你在研习机器学习时&#xff0c;最好是使用真实世界中的数据&#xff0c;而不是采用人工数据。巧的是&#xff0c;数以千计的数据集可供选择&#xff0c;涵盖了各种领域。 流行的开放数据存储库&#xff1a; OpenML.orgKaggle.compaperswithcode.com UC Irvin…

MAYA教程之模型的UV拆分与材质介绍

什么是UV 模型制作完成后&#xff0c;需要给模型进行贴图&#xff0c;就需要用到UV功能 UV编译器介绍 打开UI编译器 主菜单有一个 UV->UV编译器&#xff0c;可以点击打开 创建一个模型&#xff0c;可以看到模型默认的UV UV编译器功能使用 UV模式的选择 在UV编译器中…

让测试人头疼的这几件“小事”

对于测试而言&#xff0c;测试之旅充满了有趣的挑战和宝贵的经验教训&#xff0c;良好的测试人懂得通过项目不断总结经验与汲取教训。而成功的产品离不开PD、开发、测试全体项目伙伴的通力合作。但在实际工作中&#xff0c;大家身处的项目往往不随人意&#xff0c;下面我总结下…

洛谷 P1135 奇怪的电梯 P1135 奇怪的电梯

提供两种思路 第一种DFS 超时第九和第十点 import java.util.*; import java.io.*;public class Main{static BufferedReader br new BufferedReader(new InputStreamReader(System.in));static BufferedWriter out new BufferedWriter(new OutputStreamWriter(System.out)…

fastjson 1.2.47 远程命令执行漏洞

fastjson 1.2.47 远程命令执行漏洞 文章目录 fastjson 1.2.47 远程命令执行漏洞1 在线漏洞解读:2 环境搭建3 影响版本&#xff1a;4 漏洞复现4.1 访问页面4.2 bp抓包&#xff0c;修改参数 5 使用插件检测漏洞【FastjsonScan】5.1使用説明5.2 使用方法5.2.1 右键菜单中&#xff…

pycharm中快速对比两个.py文件

在学习一个算法的时候&#xff0c;就想着自己再敲一遍代码&#xff0c;结果最后出现了一个莫名其妙的错误&#xff0c;想跟源文件对比一下到底是在哪除了错&#xff0c;之前我都是大致定位一个一个对比&#xff0c;想起来matlab可以快速查找出两个脚本文件(.m文件)的区别&#…

Anylogic 读取和写入Excel文件

1、选择面板-连接-Excel文件&#xff0c;拖入到视图中 然后在excel文件的属性中进行绑定外部excel文件。 绑定完之后&#xff0c;在你需要读取的地方进行写代码&#xff0c; //定义开始读取的行数 //这里设为2&#xff0c;是因为第一行是数据名称 int row12; //读取excel文件信…

23面向对象案例1

目录 1、计算连续表达式的一个过程 2、优化后的代码 为什么不能return resultn&#xff1f; 3、用面向对象的方法可以解决冗余的问题&#xff0c;但是还是不能解决result的值可以被随意修改的问题 4、解决不能被随意修改的问题&#xff0c;可以将类属性改成私有变量吗&…

1.X3-Warming up

/* 此程序使用 Boost Spirit 库来解析用户提供的逗号分隔的数字列表。它演示了如何使用 Spirit 来定义解析 器和执行解析操作&#xff0c;并且在用户输入时反复执行解析操作。用户可以提供一系列逗号分隔的数字&#xff0c;程序会检查它们 是否符合指定的解析规则。如果解析成功…

acwing算法基础之数据结构--单链表

目录 1 知识点2 模板 1 知识点 一般以指针实现为主&#xff0c;可以预生成N个结构体。 struct ListNode {int val;ListNode *next; };但这里以数组模拟为主。 单链表和邻接表。 双链表。 &#xff08;一&#xff09;插入操作 链表插入操作的关键步骤&#xff0c;比如在结点…

C++位图,布隆过滤器

本期我们来学习位图&#xff0c;布隆过滤器等相关知识&#xff0c;以及模拟实现&#xff0c;需求前置知识 C-哈希Hash-CSDN博客 C-封装unordered_KLZUQ的博客-CSDN博客 目录 位图 布隆过滤器 海量数据面试题 全部代码 位图 我们先来看一道面试题 给 40 亿个不重复的无符号…

STM32成熟变频逆变器方案

该方案是一款成熟的变频逆变器的方案&#xff0c;主要是把电源从直流到3相交流的转换&#xff0c;包含变频控制板&#xff0c;逆变主板&#xff0c;IO板&#xff0c;变频控制板主控是STM32F103VET6&#xff0c;配套软件。每一块板子都是原理图和PCB一一对应&#xff0c;并且配套…

gitlab版本库安装

gitlab版本库安装 下地址 https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/ https://packages.gitlab.com/gitlab/gitlab-ce此处以 gitlab-ce-14.3.0为例 安装依赖 [rootebs-139265 opt]# sudo yum install -y curl policycoreutils-python openssh-server perl […

组件封装使用?

组件封装是指在软件开发中&#xff0c;将功能代码或数据封装成一个独立的、可重用的模块或组件。这种封装可以使得代码更加模块化、可维护性和可重用性。在许多编程语言和开发框架中&#xff0c;都有不同的方式来实现组件封装。 以下是一些常见的组件封装方法和技巧&#xff1…

如何让你的程序支持lua脚本

最近做了一个控制机械臂的程序,使用C语言开发的,调试的时候总是需要修改代码来调整运动轨迹, 总是要编译,实在烦人 不过有个方法就是使用lua来调试运动逻辑 代码如下 static int lua_up(lua_State*l) {size_t stepluaL_checkinteger(l,1);//向上动作up(step);return 0; }st…

Vue路由守卫有哪些,怎么设置,有哪些使用场景?

Vue 路由守卫是在 Vue Router 中提供的一种功能&#xff0c;它允许您在导航到某个路由前、路由变化时或导航离开某个路由时执行代码。Vue 路由守卫提供了以下几种类型&#xff1a; 1.全局前置守卫 router.beforeEach 在进入路由前执行的钩子函数&#xff0c;它会接收三个参数&a…

bat脚本字符串替换:路径中\需要替换,解决一些文件写入路径不对的问题

脚本 set dir_tmp%~dp0 set dir%dir_tmp:\\\\\% set dir_tmp%~dp0 新建一个变量dir_tmp&#xff0c;存储获取的脚本当前路径 set dir%dir_tmp:\\\\\% 新建一个变量dir &#xff0c;存储字符串替换之后的路径 其中黄色的\\实际上代表的是一个\