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

embedded/2025/3/14 8:37:57/

目录

问题引出

情况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既非指针也非引用时,我们面对的就是所谓按值传递了:

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)仅会在按值形参处被忽略。正如此前所见,若形参是const的引用或指针,expr的常量性会在型别推导过程中加以保留。但是考虑这种情况:expr是个指涉到const对象的const指针,且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的实参

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


http://www.ppmy.cn/embedded/172449.html

相关文章

EngineerCMS完整版支持OnlyOffice8.2文档协作

这次从OO5.3那个时代的接口&#xff0c;改到支持8.2接口&#xff0c;颇费周折。centos升级和docker升级 - Powered by MinDoc (itdos.net) 1. 首先是升级centos 手动升级centos7内核&#xff08;版本自行选择&#xff0c;亲测内核下载链接有效&#xff09;_centos内核下载-CS…

微信小程序防止弹框下面穿透滚动

‌使用catchtouchmove属性‌&#xff1a;在需要防止穿透滚动的元素上添加catchtouchmove"true"属性。这样&#xff0c;当用户在该元素上进行滚动操作时&#xff0c;不会触发下层的滚动事件&#xff0c;从而防止穿透滚动。 例如&#xff1a; 修改前&#xff0c;在弹框…

JAVA面试_进阶部分_Java JVM:垃圾回收(GC 在什么时候,对什么东西,做了什么事情)

在什么时候&#xff1a; 首先需要知道&#xff0c;GC又分为minor GC 和 Full GC&#xff08;major GC&#xff09;。Java堆内存分为新生代和老年代&#xff0c;新生代 中又分为1个eden区和两个Survior区域。 一般情况下&#xff0c;新创建的对象都会被分配到eden区&#xff…

【算法day9】字符串转换整数 (atoi);请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数。

字符串转换整数 (atoi) https://leetcode.cn/problems/string-to-integer-atoi/description/ 请你来实现一个 myAtoi(string s) 函数&#xff0c;使其能将字符串转换成一个 32 位有符号整数。 函数 myAtoi(string s) 的算法如下&#xff1a; 空格&#xff1a;读入字符串并丢…

FiddlerScript学习笔记

参考官方文档&#xff1a;https://www.fiddlerbook.com/fiddler/dev/scriptsamples.asp json // 反序列化 static function jsonDecode(str : String){return Fiddler.WebFormats.JSON.JsonDecode(str).JSONObject; } // 序列化 static function jsonEncode(jsonObject : Obje…

Doris 数据划分:分区与分桶策略全解析

在 Doris 的分布式架构里&#xff0c;数据划分策略是实现高效存储和查询的关键所在。它主要依靠分区&#xff08;Partition&#xff09;和分桶&#xff08;Bucket&#xff09;这两层逻辑划分&#xff0c;对数据的分布进行精细化管理。本文将深入探讨这两种策略的设计思路、实际…

4-002:如何使用 MySQL 的 EXPLAIN 语句进行查询分析?

EXPLAIN 是 MySQL 中用于分析查询性能的工具&#xff0c;能够帮助你理解查询的执行计划。通过 EXPLAIN&#xff0c;你可以查看 MySQL 如何执行查询&#xff0c;包括使用的索引、表连接顺序等信息。 基本用法 在查询前加上 EXPLAIN 即可&#xff1a; EXPLAIN SELECT * FROM y…

在 Linux 64 位系统上安装 Oracle 11g R2 数据库的完整指南

linux.x64_11gR2_database 是 Oracle 数据库 11g 第 2 版&#xff08;11g Release 2&#xff09;的安装包&#xff0c;适用于 64 位 Linux 操作系统。这个安装包包含了在 64 位 Linux 系统上安装 Oracle 数据库所需的全部文件和组件。 安装步骤概述 以下是在 Linux 系统上安装…