当前位置: 首页 > news >正文

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

目录

问题引出

情况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.mrgr.cn/news/94329.html

相关文章:

  • Kubernetes教程(九)了解卷volume的emptyDir和hostPath
  • 将串口接收到的十六进制数据转为十进制
  • ⭐算法OJ⭐汉明距离【位操作】(C++ 实现)Hamming Distance
  • 【vue + JS】OCR图片识别、文字识别
  • 《基于大数据的营养果蔬推荐系统的设计与实现》开题报告
  • 在 Windows 上快速部署 OpenManus:从安装到运行
  • 计算机网络——DHCP实验
  • python -面试题--算法
  • RGV调度算法(三)--遗传算法
  • LeetCode 解题思路 15(Hot 100)
  • 独立开发记录:使用Trae和Cloudflare快速搭建了自己的个人博客
  • ES6回顾:闭包->(优点:实现工厂函数、记忆化和异步实现)、(应用场景:Promise的then与catch的回调、async/await、柯里化函数)
  • 【C#学习笔记04】C语言格式化输出
  • 深度剖析 Doris 数据倾斜,优化方案一网打尽
  • 【二分查找 寻找首端】P3718 [AHOI2017初中组] alter|普及+
  • uniapp实现 uview1 u-button的水波纹效果
  • 使用memmove优化插入排序
  • 新闻网页信息抽取
  • JVM 垃圾回收器的选择
  • 广播机制(Broadcasting)