条款1:理解模版性别推导

devtools/2025/3/14 14:37:39/

目录

问题引出

情况1:ParamType是个指针或引用,但不是个万能引用。

情况2:ParamType是个万能引用

情况3:ParamType既非指针也非引用


问题引出

函数模板大致形如:

template<typename T>
void f(ParamType param);

而一次调用则形如:

f(expr)  //以某表达式调用f

在编译期,编译器会通过expr推导两个型别:一个是T的型别,另一个是ParamType的型别,这两个型别往往不一样。因为,ParamType常会包含了一些饰词,如const或引用符号等限定词。例如,若模板声明如下:

template<typename T>
void f(const T& param);  //ParamType是const T&

而调用语句如下:

int x = 0;f(x);     //以一个int调用f

在此例中,T被推导为int,而ParamType则被推导为const int&。

我们很自然地会认为,T的型别推导结果和传递给函数的实参型别是同一的。换句话说,T的型别就是expr的型别。在上例中,情况确乎如此:x的型别是int,T的型别也推导为int。但是,这一点并不总是成立。T的型别推导结果,不仅仅依赖于expr的型别,还依赖ParamType的形式。具体要分三种情况讨论:

  • ParamType具有指针或引用型别,但不是个万能引用。
  • ParameType是一个万能引用。
  • ParamType既非指针也非引用。

这么一来,我们就有了三种型别推导场景进行分情况考察。在对它们逐一考察时,我们仍采用前述模板和调用的一般形式。

template<typename T>
void f(ParamType param);f(expr);  //从expr来推导T和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是x的型别为const int的引用

在各次调用中,对param和T的型别推导结果如下:

f(x);    // T的型别是int,param的型别是int&

f(cx);  // T的型别是const int,param的型别是const int&

f(rx);  // T的型别是const int,param的型别是const int&

在第二个以及第三个调用语句中,由于cx和rx的值都被指明为const,所以T的型别被推导为const int,从而形参的型别就成了const int&。这一点对于调用者来说至关重要。当人们向引用型别的形参传入const对象时,它们期望该对象保持其不可修改的属性,也就是说,期望该形参成为const的引用型别。这也是为何向持有  T& 型别的模板传入 const 对象是安全的:该对象的常量性(constness)会成为T的型别推导结果的组成部分。

在第三个调用中,请注意,即使rx具有引用型别,T也并未被推导成一个引用。原因在于,rx的引用性(reference-ness)会在型别推导过程中被忽略。

尽管上述调用语句示例演示的都是左值引用形参,但是右值引用形参的型别推导运作方式是完全相同的。当然,传给右值引用形参的,只能是右值引用实参,但这个限定和型别推导无关。

如果我们将形参型别从 T& 改为 const T& ,结果会有一点变化,但这些变化并没有什么出人意料之处。cx和rx的常量性仍然得到了满足,但是由于我们现在会假定param具有const应用型别,T的型别推导结果中包含 const 也就没有必要了。

template<typename T>
void f(const T& param);    // param现在是个const引用了int 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&

一如前例,rx的引用性在型别推导过程中是被忽略的。

如果 param 是个指针(或指涉到 const 对象的指针)而非引用,运作方式本质上并无不同:

template<typename T>
void f(T* param)      // param 现在是个指针了int x = 27;           // 同前
const int *px = &x;   // px是指涉到x的指针,型别为const intf(&x);                // T的型别是int,param的型别是int*
f(px);                // T的型别是const int,param的型别是const int*

情况2:ParamType是个万能引用

对于持有万能引用形参的模板而言,规则就不那么明显了。此类形参的声明方式类似右值引用(即在函数模板中持有型别形参T时,万能引用的声明型别写作 T&& ),但是当传入的实参是左值时,其表现会有所不同。

  • 如果 expr 是个左值,T 和 ParamType都会被推导为左值引用。这个结果具有双重奇特之处:首先,这是在模板型别推导中,T被推导为引用型别的唯一情形。其次,尽管在声明时使用的是右值引用语法,它的型别推导结果却是左值引用。
  • 如果 expr 是个右值,则应用“常规”(即情况1中的)规则。

例如:

template<typename T>
void f(T&& param);     // param现在是个万能引用int x = 27;            // 同前
const int cx = x;      // 同前
const int& rx = x;     // 同前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)了:

template<typename T>
void f(T param);     //param现在是按值传递

这意味着,无论传入的是什么,param都会是它的一个副本,也即一个全新对象。param会是个全新对象这一事实促成了如何从expr推导T的型别的规则:

  • 一如之前,若expr具有引用型别,则忽略其引用部分。
  • 忽略expr的引用性之后,若expr是个const对象,也忽略之。若其是个 volatile 对象,同忽略之(volatile对象不常用,它们一般仅用于实现设备驱动程序。)

所以,

int x = 27;          // 同前
const int cx = x;    // 同前
const int &rx = x;   // 同前f(x);                // T和param的型别都是int
f(cx);               // T和param的型别还都是int
f(rx);               // T和param的型别仍都是int

请注意,即使cx和rx代表const值,param仍然不具有const型别。这是合理的。param是个完全独立于cx和rx存在的对象——是cx和rx的一个副本。从而cx和rx不可修改这一事实并不能说明param是否可以修改。正是由于这一原因,expr的常量性以及挥发性(volatileness,若有)可以在推导param的型别时加以忽略:仅仅由于expr不可修改,并不能断定其副本也不可修改。

需要重点说明的是,const(和volatile)仅会在按值(by value)形参处被忽略。正如此前所见,若形参是const的引用或指针,expr的常量性会在型别推导过程中加以保留。但是考虑这种情况:expr是个指涉到const对象的const指针(a const pointer to a const object),且expr按值传给param:

template<typename T>
void f(T param);         // param仍按值传递const char* const ptr = "Fun with pointers";  // ptr是个指涉到const对象的const指针f(ptr);                  // 传递型别为const char * const的实参

这里位于星号(asterisk)右侧的const将ptr声明为const:ptr不可以指涉到其他内存位置,也不可以被置为null【位于星号左侧的const则将ptr指涉到的对象(那个字符串)为const,即将字符串不可修改】。可ptr被传递给f时,这个指针本身将会按比特复制给param。换言之,ptr这个指针自己会被按值传递。依照按值(by value)传递形参的型别推导规则,ptr的常量性会被忽略,param的型别会被推导为const char *,即一个可修改的、指涉到一个const字符串的指针(a modifiable pointer to a const character string)。在型别推导的过程中,ptr指涉到的对象的常量性会得到保留,但其自身的常量性则会以复制方式创建新指针param的过程中被忽略。


http://www.ppmy.cn/devtools/167046.html

相关文章

2025-03-13 禅修-错误的做法

摘要: 2025-03-13 禅修-错误的做法 禅修-错误的做法 我们今天的课程是这个禅修防误。主要是有一些我们所明令禁止的。在整个禅修过程中&#xff0c;会对我们禅修出现一些弊端的这部分&#xff0c;我们会给大家介绍。第一&#xff0c;在禅修中要防止自由联想&#xff0c;防止幻…

django中间件说明

Django中间件是一种在请求和响应处理过程中介入的机制&#xff0c;允许你在视图处理请求之前或之后执行自定义代码。中间件适用于处理全局性任务&#xff0c;如身份验证、日志记录、内容修改等。以下是Django中间件的详细说明和使用方法&#xff1a; 一、中间件的核心概念 作用…

OSPF-2 邻接建立关系

上一期我们说了OSPF的邻居建立关系以及OSPF邻居关系建立中建立失败的因素以及相关实验案例 这一期我们来说说OSPF的邻接关系建立时需要交互哪些报文以及失败因素及原因和相关实验案例 一、概述 在运行了OSPF的网络当中为了交互链路状态信息和路由信息,互相之间需要建立邻接关…

Linux云计算SRE-第二十周

完成ELK综合案例里面的实验&#xff0c;搭建完整的环境 一、 1、安装nginx和filebeat&#xff0c;配置node0(10.0.0.100)&#xff0c;node1(10.0.0.110)&#xff0c;node2(10.0.0.120)&#xff0c;采用filebeat收集nignx日志。 #node0、node1、node2采用以下相同方式收集ngin…

【 <一> 炼丹初探:JavaWeb 的起源与基础】之 JavaWeb 项目的部署:从开发环境到生产环境

<前文回顾> 点击此处查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、开发环境…

Vue项目搜索引擎优化(SEO)终极指南:从原理到实战

文章目录 1. SEO基础与Vue项目的挑战1.1 为什么Vue项目需要特殊SEO处理&#xff1f;1.2 搜索引擎爬虫工作原理 2. 服务端渲染&#xff08;SSR&#xff09;解决方案2.1 Nuxt.js框架实战原理代码实现流程图 2.2 自定义SSR实现 3. 静态站点生成&#xff08;SSG&#xff09;技术3.1…

vue 知识点整理

1.data为什么是一个函数而不是对象 维度对象形式函数形式数据隔离性所有实例共享同一对象&#xff0c;导致数据污染每个实例拥有独立数据副本复用安全性不适用于可复用组件支持组件安全复用语言机制引用传递引发副作用函数返回值实现作用域隔离&#xff08;闭包&#xff09;框…

OpenCV(应用) —— 凸包检测的实战应用

文章目录 一、凸包的概念二、Opencv中的API三、应用场景与实战3.1、实战场景一一、凸包的概念 常见的找寻目标的外轮廓有矩形框(如最小外接矩阵)和圆形框,但这种包围框为了保持几何形状,与图形的真实轮廓贴合度较差。如果能找出图形最外层的端点,将这些端点连接起来,就可…