跟我学C++中级篇——函数模板的匹配

一、介绍和说明

在本文会尝试着把函数的重载以及模板自动推导等方法结合起来,一起分析模板函数的匹配的方式和原则。在普通的函数重载和普通的模板函数中,都比较容易理解调用哪一类,但在一些较为少用或者复杂的情况下,可能会发现一些特别的情况。这篇文章会针对这些情况进行一些具体的分析,并尝试着把一些疑惑解答分析出来。

二、函数的重载

函数的重载本身是比较简单的,即函数文件名相同但参数不同(注意,返回值不同不可以做为重载的判定),这里的参数不同有一个注意点,它指参数的个数不同或者类型不同,另外一个比较容易被忽略的是指参数的类型不同,是指按顺序(C/C++默认自右向左压栈参数)索引的严格不同。也就是说如果三个参数三个类型,只要排序不同,便认为是重载。
普通函数的重载需要注意的是auto的引入,这需要一个推导的过程,不过在实际应用中,一般很少使用auto做为模板参数,而且即使推导也相对来说比较模板要简单很多。
函数的重载麻烦在模板函数的重载,或者函数模板的实例化形成与普通函数共存时的匹配问题。
函数模板有几种方式被实例化:
1、全特化
非常遗憾的告诉大家,函数模板全特化后的函数,并不参与重载。这也是在大牛们的文章中“T.144: Don’t specialize function templates” 和“Why Not Specialize Function Templates? ”中都不建议使用函数模板的全特化而是强烈推荐使用普通函数的重载。
当然,如果一定要使用特化的方法,推荐是使用类模板封装这个函数模板,再进行类似的操作。
2、显示实例化
显示实例已经类似于普通函数了,这个其实和写普通函数没啥质的区别了。通过一般是库的开发者为了提高编译速度而应用的一种技巧。

函数模板的偏特化没有实际意义,所以其没有偏特化的处理。这就回到了教科书上的说法:
1、优先匹配普通函数
2、普通模板函数
3、如果匹配到2,其拥有全特化模板函数,则使用其。

三、模板的偏特化问题

刚刚分析过,函数模板没有偏特化,那为什么类模板有偏特化而函数模板没有偏特化呢?在前面的文章中也分析过,相关的书籍也有说明,很简单,通过偏特化来实现函数的不同的选择,基本没有什么意义。
回顾一下偏特化,就是将模板参数的部分进行特化也就是显示指定类型。对函数来说,这和写一个重载的函数有何不同?而且还不如函数重载更容易理解和实现。下面看几个小例子:

#include <iostream>int TestOL(int d) { std::cout << "call TestOL int: " << d << std::endl; return 0; }
void TestOL(double d) { std::cout << "call TestOL double:" << std::endl; }
int TestOL(int d, int d1) { std::cout << "call TestOL int,int " << d << "," << d1 << std::endl; return 0; }template<typename T>
int TestOL(T t) { std::cout << "call template TestOL T:" << t << std::endl; return 0; }
template<typename T1,typename T2>
int TestOL(T1 t1,T2 t2) { std::cout << "call template TestOL t1,t2:"<<t1<<","<<t2 << std::endl; return 0;
}
//模板函数与int函数重载,采用普通int函数,所以模板函数的全特化就无法使用
template<>
int TestOL(int t) { std::cout << "call template specialize TestOL:"<<t << std::endl;  return 0;
}
template int TestOL(int t);
//C++标准要求是如果一个模板被显示实例化后,则不能被全特化,被忽略。这可以做为一个技巧,只在明确显示实例化的编译单元进行非特化处理
//一个函数模板显示实例化只能有一次
template int TestOL<int, int>(int t, int t1);//注意,不能有实现void TestFuncOL() {int d = 111;TestOL(100);TestOL(d);TestOL(d,100);double db = 3.3;float df = 3.f;TestOL(db);TestOL(df);TestOL(db,df);TestOL<int>(1);//显示调用全特化版本TestOL(db, 6);TestOL<int,int>(1,6);//调用显示实例化版本
}
int main()
{TestFuncOL();return 0;
}

运行结果:

call TestOL int: 100
call TestOL int: 111
call TestOL int,int 111,100
call TestOL double:
call template TestOL T:3
call template TestOL t1,t2:3.3,3
call template specialize TestOL:1
call template TestOL t1,t2:3.3,6
call template TestOL t1,t2:1,6

可以在这个小例子上自行扩大测试范围,就会有清晰的理解了。所以说偏特化在函数模板上真得是没啥用处。
另外需要注意编译通过与具体调用实现的问题,这个就涉及到模板内部的延迟加载的问题了,这个问题与重载有关系,但又有不同,有兴趣可以查看下相关的书籍。
所以说,一句话,简单着来,不要自己给自己没事儿找事儿。

四、模板的匹配

好,有了前面的铺垫,可以进行模板的匹配的分析了。

1、重载的匹配
先看一个例子:

//1:标准模板
template<typename T>
void testspec(T t) { std::cout << "call template testspec T:" << t << std::endl; }//2:此处和4相同
template<> void testspec<>(int* t) { std::cout << "call template testspec *int, t:" << *t << std::endl; }//3:此时调用此处
template<typename T>
void testspec(T* t) { std::cout << "call template testspec *T, t:" << *t << std::endl; }//4:此处和2相同,解开注释,并把2注释掉后会调用此处。
//template<> void testspec<>(int *t) { std::cout << "call template testspec *int, t:" << *t << std::endl; }void TestS()
{int data = 10;int* ptr = &data;testspec(ptr);
}

把2和4调换一下位置,则会发现产生的结果不同,他们的运行结果如标注一中所示。原因在于二者的编译虽然会产生几乎相同的结果,但是,由于重载的产生导致的结果不同。在上面的代码中,注释未解开时,2是1的全特化,2和3此时产生重载,3更合适,故而会调用3的情况;但在注释2并打开4注释时,1和3重载,4是3的特化,所以调用4。此处一定要明白,重载时,特化的函数是不参与重载的,即可清楚。

2、返回值时的匹配

下面再看一个例子:

int TestFunc(int d) {std::cout << "this is test!" << std::endl;return d + 100;
}
template <typename R, typename F, typename... Args,class = typename std::enable_if<!std::is_member_function_pointer<F>::value>::type>
void funcPack(F &&f, Args &&...args) {std::function<R()> m_func = [&] { return f(args...); };
}template <typename F, typename... Args>
void GetClassFromName(F &&f, Args... args) { f(std::forward<Args>(args)...); }void TestGetNameFunc() {GetClassFromName(TestFunc, 100);funcPack<int>(TestFunc, 200);//此处没有int会是什么样?
}

此时的匹配注意点在funcPack这个函数上,特别是R这个返回值,大家仔细想一想注释中的问题,就明白了匹配的的另外一种情况了。
3、可以隐式提升的情况

看一下例子:

template<typename T>
void getData(T t) { std::cout << "call template getData   t:" << t << std::endl; }
void getData(int t) { std::cout << "call   getData  t, t:" << t << std::endl; }
int  main() {short d = 9;getData(9);//调用普通函数return 0;
}

4、有CV限定符的情况
5、均为模板时,显示实例化优先
6、C++11后的decltype匹配
看下面的例子:

template <typename T1,typename T2>
auto getValue(T1 t1, T2 t2)
//->decltype(t1+t2) //c++14拖尾类型
//->typename std::decay<decltype(t1+t2)>::type  //如果想去除cv限定
{decltype(t1 + t2) n = t1 + t2;std::cout << "cur n :" << n << std::endl;return n;
}
int main() {int d1 = 10;double d2 = 3.3;getValue(d1,d2);return 0;
}

从上面的几点可以总结出匹配的原则来:
1、普通函数优先即完全匹配
2、完全匹配时如果可以自动转换(提升类型或者隐式转换),仍然使用普通函数
3、完全匹配时,如果有CV限定等,严格按照有无匹配(但仍然是普通函数优先)
4、如果均为模板函数,则有显示实例化的优先
5、如果均为模板函数,4之后,编译器认为哪个需要处理的情况简单哪个优先(如前面的T和T*即如此)
6、decltype匹配中,无括号的标识符的,则类型与标识符一致;如果为表达式,则只检查得到函数返回值类型并保持一致;如果表达式为左值,则为一个指向其的引用;最后,以上均不符合时,则只符合表达式的类型即可。
再次重申一次,不要自找麻烦,想写重载时不要用模板的特化来实现(因为特化不参与重载),如果写函数模板,尽量将其写成单个函数模板也就是前面提到的用类模板封装其为静态函数模板(库里很多应用)。

编写模板函数时的注意点:
1、尽量要不写参与重载的模板函数的全特化,这等于自找麻烦
2、要注意模板的显示实例化和全特化及普通函数的不同
3、注意函数模板的延迟加载

五、总结

开发的过程其实是一个思维抽象再实践的过程。这个过程只有一条正确的路,就是用最简单(或者说尽可能简单)的方式来实现功能。所有的编程技巧,其终极的目标只有一个,让功能实现变得容易和安全,让代码更容易维护。当然,理想是美好的,但实践起来可能需要取舍,比如代码安全了,可能维护就复杂一些,这就看开发者的设计目标和实际场景的要求了。
总之,就是古人常说的“大道至简”!

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

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

相关文章

《MySQL 简易速速上手小册》第7章:MySQL监控和日志分析(2024 最新版)

文章目录 7.1 配置和使用 MySQL 监控工具7.1.1 基础知识7.1.2 重点案例&#xff1a;使用 Python 和 Prometheus 监控 MySQL 性能7.1.3 拓展案例 1&#xff1a;自动化 MySQL 慢查询日志分析7.1.4 拓展案例 2&#xff1a;实时警报系统 7.2 解读 MySQL 日志文件7.2.1 基础知识7.2.…

使用django构建一个多级评论功能

&#xff0c;评论系统是交流和反馈的重要工具&#xff0c;尤其是多级评论系统&#xff0c;它允许用户回复特定评论&#xff0c;形成丰富的对话结构。这个文章是使用Django框架从零开始构建一个多级评论系统。Django是一个高级Python Web框架&#xff0c;它鼓励快速开发和干净、…

FastAPI使用异步motor实现对mongodb的增删改查

一、环境准备&#xff1a; 需要有mongodb和poetry以及Python3.10 二、克隆示例代码到本地 git clone gitgithub.com:waketzheng/fastapi_async_mongo_demo.git 三、安装依赖 cd fastapi_async_mongo_demo poetry shell poetry install 四、启动服务 python app/main.py …

Qt中程序发布及常见问题

1、引言 当我们写好一个程序时通常需要发布给用户使用&#xff0c;那么在Qt中程序又是如何实现发布的呢&#xff0c;这里我就来浅谈一下qt中如何发布程序&#xff0c;以及发布程序时的常见问题。 2、发布过程 2.1、切换为release模式 当我们写qt程序时默认是debug模式&#x…

ICCV 2023 | 8篇论文看扩散模型diffusion用于图像检测任务:动作检测、目标检测、异常检测、deepfake检测...

1、动作检测 DiffTAD: Temporal Action Detection with Proposal Denoising Diffusion 基于扩散方法提出一种新的时序动作检测&#xff08;TAD&#xff09;算法&#xff0c;简称DiffTAD。以随机时序proposals作为输入&#xff0c;可以在未修剪的长视频中准确生成动作proposals。…

【爬虫作业】python爬虫作业——爬取汽车之家

爬取汽车之家期末作业&#xff1a; 代码如下所示&#xff1a; import random import timeimport requests #发送网络请求 import parsel import csv # 1.发送网络请求 headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like G…

CVE-2012-2311 漏洞复现

CVE-2012-2311 这个漏洞被爆出来以后&#xff0c;PHP官方对其进行了修补&#xff0c;发布了新版本5.4.2及5.3.12&#xff0c;但这个修复是不完全的&#xff0c;可以被绕过&#xff0c;进而衍生出CVE-2012-2311漏洞。 PHP的修复方法是对-进行了检查&#xff1a; if(query_str…

动漫风博客介绍页面源码

动漫风博客介绍页面源码&#xff0c;HTML源码&#xff0c;图片背景有淡入切换特效 蓝奏云&#xff1a;https://wfr.lanzout.com/iIDZu1nrmjve

Go内存优化与垃圾收集

Go提供了自动化的内存管理机制&#xff0c;但在某些情况下需要更精细的微调从而避免发生OOM错误。本文介绍了如何通过微调GOGC和GOMEMLIMIT在性能和内存效率之间取得平衡&#xff0c;并尽量避免OOM的产生。原文: Memory Optimization and Garbage Collector Management in Go 本…

codeforces 1400分

文章目录 1.[B. Phoenix and Beauty](https://codeforces.com/problemset/problem/1348/B)2.[C. Rotation Matching](https://codeforces.com/problemset/problem/1365/C)3.[C. Element Extermination](https://codeforces.com/problemset/problem/1375/C)4.[D. Epic Transform…

Java编程构建高效二手交易平台

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

二、Mybatis相关概念

1.对象/关系数据库映射&#xff08;ORM) ORM全称Object/Relation Mapping&#xff1a;表示对象-关系映射的缩写ORM完成面向对象的编程语言到关系数据库的映射。当ORM框架完成映射后&#xff0c;程序员既可以利用面向对象程序设计语言的简单易用性&#xff0c;又可以利用关系数…

【JS逆向三】逆向某某网站的sign参数,并模拟生成仅供学习

逆向日期&#xff1a;2024.02.06 使用工具&#xff1a;Node.js 类型&#xff1a;webpack 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 可使用AES进行解密处理&#xff08;直接解密即可&#xff09;&#xff1a;AES加解密工具 1、打开某某…

node.js 读目录.txt文件,用 xml2js 转换为json数据,生成jstree所需的文件

请参阅&#xff1a;java : pdfbox 读取 PDF文件内书签 请注意&#xff1a;书的目录.txt 编码&#xff1a;UTF-8&#xff0c;推荐用 Notepad 转换编码。 npm install elementtree ; npm install xml2js ; node.js 用 elementtree读目录.txt文件&#xff0c;用 xml2js 转换为…

MySQL篇----第十七篇

系列文章目录 文章目录 系列文章目录前言一、对于关系型数据库而言,索引是相当重要的概念,请回答有关索引的几个问题二、解释 MySQL 外连接、内连接与自连接的区别三、Myql 中的事务回滚机制概述前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分…

来自谷歌的新年礼物!速来免费领取2个月谷歌Gemini Advanced会员!价值280元!对标ChatGPT Plus!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

详细了解Node.js的配置与使用!

详细了解Node.js的配置与使用&#xff01; Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。它允许开发者在服务器端运行 JavaScript&#xff0c;从而实现全栈 JavaScript 开发。本文将介绍 Node.js 的配置和 npm 的应用。 一、Node.js 配置 下载与安装 首先&…

【iOS】——使用ZXingObjC库实现条形码识别并请求信息

文章目录 前言一、实现步骤二、扫描界面和扫描框的样式1.扫描界面2.扫描框 三、实现步骤 前言 ZXing库是一个专门用来解析多种二维码和条形码&#xff08;包括包括 QR Code、Aztec Code、UPC、EAN、Code 39、Code 128等&#xff09;的开源性质的处理库&#xff0c;而ZingObjC库…

1184. 欧拉回路(欧拉回路,模板题)

活动 - AcWing 给定一张图&#xff0c;请你找出欧拉回路&#xff0c;即在图中找一个环使得每条边都在环上出现恰好一次。 输入格式 第一行包含一个整数 t&#xff0c;t∈{1,2}&#xff0c;如果 t1&#xff0c;表示所给图为无向图&#xff0c;如果 t2&#xff0c;表示所给图为…

网络编程..

1.互联网 有了互联网的出现 我们就可以足不出户的实现看电影、购物等等操作 我们认知中可能的互联网模型 较为真实的互联网模型 那么数据是如何从一个设备传递到另外一个设备的呢&#xff1f; 2.网络互联模型 统共有三种&#xff1a; 3.TCP/IP协议 TCP/IP是一群协议 里面…