C++重载和模板

重载与模板

函数模板可以被另一个模板或一个普通非模板函数重载。

与往常一样,名字相同的函数必须具有不同数量或类型的参数。

如果涉及函数模板,则函数匹配规则会在以下几方面受到影响:

  1. 对于一个调用,其候选函数包括所有模板实参推断成功的函数模板实例。
  2. 候选的函数模板总是可行的,因为模板实参推断会排除任何不可行的模板。
  3. 与往常一样,可行函数(模板与非模板)按类型转换(如果对此调用需要的话)来排序。当然,可以用于函数模板调用的类型转换是非常有限的。
  4. 与往常一样,如果恰有一个函数提供比任何其他函数都更好的匹配,则选择此函数。但是,如果有多个函数提供同样好的匹配,则:

              ——如果同样好的函数中只有一个是非模板函数,则选择此函数,

               ——如果同样好的函数中没有非模板函数,而有多个函数模板,且其中一个模板比其他                          模板更特例化,则选择此模板。

               ——否则,此调用有歧义。

正确定义一组重载的函数模板需要对类型间的关系及模板函数允许的有限的实参类型转换有深刻的理解。

编写重载模板

作为一个例子,我们将构造一组函数,它们在调试中可能很有用。

我们将这些调试函数命名为debug_rep,每个函数都返回一个给定对象的string表示。

我们首先编写此函数的最通用版本,将它定义为一个模板,接受一个const对象的引用:

//打印任何我们不能处理的类型
template <typename T> 
string debug_rep(const T& t)
{ostringstream ret;ret << t; // 使用T的输出运算符打印t的一个表示形式return ret.str();// 返回ret 绑定的string的一个副本
}

此函数可以用来生成一个对象对应的string表示,该对象可以是任意具备输出运算符的类型。

接下来,我们将定义打印指针的debug_rep版本:


template <typename T> 
string debug_rep(T* p)
{cout << "使用了T*p版本" << endl;ostringstream ret;  // 打印指针本身的值ret << "pointer: " << p;if (p)ret << "" << debug_rep(*p);// 打印p指向的值elseret << " null pointer"; // 或指出ρ为空return ret.str();// 返回ret 绑定的string的一个副本
}

此版本生成一个string,包含指针本身的值和调用debug_rep获得的指针指向的值。

注意此函数不能用于打印字符指针,因为IO库为char+值定义了一个<<版本。此<版本假定指针表示一个空字符结尾的字符数组,并打印数组的内容而非地址值。

我们可以这样使用这些函数:

#include<iostream>
#include<sstream>
using namespace std;
//打印任何我们不能处理的类型
template <typename T> 
string debug_rep(const T& t)
{cout << "使用了const T&t版本" << endl;ostringstream ret;ret << t; // 使用T的输出运算符打印t的一个表示形式return ret.str();// 返回ret 绑定的string的一个副本
}template <typename T> 
string debug_rep(T* p)
{cout << "使用了T*p版本" << endl;ostringstream ret;  // 打印指针本身的值ret << "pointer: " << p;if (p)ret << "" << debug_rep(*p);// 打印p指向的值elseret << " null pointer"; // 或指出ρ为空return ret.str();// 返回ret 绑定的string的一个副本
}int main()
{string s("hi");cout << debug_rep(s)<<endl;
}

对于这个调用,只有第一个版本的debug_rep是可行的。

第二个debug_rep版本要求一个指针参数,但在此调用中我们传递的是一个非指针对象。

因此编译器无法从一个非指针实参实例化一个期望指针类型参数的函数模板,因此实参推断失败。

由于只有一个可行函数,所以此函数被调用。

如果我们用一个指针调用 debug_rep:

cout << debug_rep(&s)<<endl;

两个函数都生成可行的实例:

  1. debug _rep (const string*&),由第一个版本的debug_rep实例化而来,被绑定到string*。
  2. debug_rep(string*),由第二个版本的debug_rep实例化而来,T被绑定到string。

第二个版本的debug rep的实例是此调用的精确匹配。

第一个版本的实例需要进行普通指针到 const指针的转换。

正常函数匹配规则告诉我们应该选择第二个模板,实际上编译器确实选择了这个版本。

#include<iostream>
#include<sstream>
using namespace std;
//打印任何我们不能处理的类型
template <typename T> 
string debug_rep(const T& t)
{cout << "使用了const T&t版本" << endl;ostringstream ret;ret << t; // 使用T的输出运算符打印t的一个表示形式return ret.str();// 返回ret 绑定的string的一个副本
}template <typename T> 
string debug_rep(T* p)
{cout << "使用了T*p版本" << endl;ostringstream ret;  // 打印指针本身的值ret << "pointer: " << p;if (p)ret << "" << debug_rep(*p);// 打印p指向的值elseret << " null pointer"; // 或指出ρ为空return ret.str();// 返回ret 绑定的string的一个副本
}
int main()
{string s("hi");string result = debug_rep(&s); // 将函数返回值存储在result变量中  cout << result << endl; // 打印result变量,即debug_rep函数的返回值  
}

我们打开调试面板,在执行函数调用语句时,直接跳进了第二个模板函数

有人就好奇了为什么会出现第二行的提示

这里的关键在于模板函数的实例化和递归调用。当您调用 debug_rep(&s) 时,由于传递了一个指针,所以编译器会选择第二个模板函数 debug_rep(T* p) 进行实例化。在这个函数内部,当指针 p 不为空时,会递归调用 debug_rep(*p)

递归调用 debug_rep(*p) 时,传递的是指针 p 所指向的值,即字符串 s 的一个引用。因此,编译器会选择第一个模板函数 debug_rep(const T& t) 进行实例化,其中 T 被推导为 std::string

在第一个模板函数内部,cout 语句会首先执行,打印出 "使用了const T&t版本"。接着,函数会使用输出运算符 << 将 t(即字符串 s)插入到一个 ostringstream 对象中,并最终返回这个对象转换成的字符串。

因此,当您运行程序时,会看到 "使用了const T&t版本" 被打印出来,这是因为在递归调用中第一个模板函数被实例化并执行了。

多个可行模板

作为另外一个例子,考虑下面的调用:

const string *sp = &s;
cout << debug_rep(sp) << endl;

此例中的两个模板都是可行的,而且两个都是精确匹配:

  • debug_rep(const string*&),由第一个版本的debug_rep实例化而来,T被绑定到string*。
  • debug_rep(const string*),由第二个版本的debug rep 实例化而来,T被绑定到 const string。

在此情况下,正常函数匹配规则无法区分这两个函数。我们可能觉得这个调用将是有歧义的。

但是,根据重载函数模板的特殊规则,此调用被解析为debug rep(T*),即,更特例化的版本。

设计这条规则的原因是,没有它,将无法对一个const的指针调用指针版本的debug_rep。

问题在于模板 debug rep(const T&)本质上可以用于任何类型,包括指针类型。此模板比debug_rep(T*)更通用,后者只能用于指针类型。没有这条规则,传递const的指针的调用永远是有歧义的。

当有多个重载模板对一个调用提供同样好的匹配时,应选择最特例化的版本。

#include<iostream>
#include<sstream>
using namespace std;
//打印任何我们不能处理的类型
template <typename T> 
string debug_rep(const T& t)
{cout << "使用了const T&t版本" << endl;ostringstream ret;ret << t; // 使用T的输出运算符打印t的一个表示形式return ret.str();// 返回ret 绑定的string的一个副本
}template <typename T> 
string debug_rep(T* p)
{cout << "使用了T*p版本" << endl;ostringstream ret;  // 打印指针本身的值ret << "pointer: " << p;if (p)ret << "" << debug_rep(*p);// 打印p指向的值elseret << " null pointer"; // 或指出ρ为空return ret.str();// 返回ret 绑定的string的一个副本
}
int main()
{string s("hi");const string* sp = &s;cout << debug_rep(sp) << endl;
}

事实证明,确实是调用了T*版本

非模板和模板重载

作为下一个例子,我们将定义一个普通非模板版本的debug_rep来打印双引号包围的string:

string debug_rep(const string& s)
{return '"' + s + '"';
}


现在,当我们对一个string 调用debug_rep时:

string s("hi");cout << debug_rep(s) << endl;


有两个同样好的可行函数:

  • debug_rep<string>(const string&),第一个模板,T被绑定到string*。
  • debug_rep(const string&),普通非模板函数。

在本例中,两个函数具有相同的参数列表,因此显然两者提供同样好的匹配。但是,编译委会选择非模板版本。

当存在多个同样好的函数模板时,编译器选择最特例化的版本,出于相同的原因, 一个非模板函数比一个函数模板更好。

#include<iostream>
#include<sstream>
using namespace std;
//打印任何我们不能处理的类型
template <typename T> 
string debug_rep(const T& t)
{cout << "使用了const T&t版本" << endl;ostringstream ret;ret << t; // 使用T的输出运算符打印t的一个表示形式return ret.str();// 返回ret 绑定的string的一个副本
}template <typename T> 
string debug_rep(T* p)
{cout << "使用了T*p版本" << endl;ostringstream ret;  // 打印指针本身的值ret << "pointer: " << p;if (p)ret << "" << debug_rep(*p);// 打印p指向的值elseret << " null pointer"; // 或指出ρ为空return ret.str();// 返回ret 绑定的string的一个副本
}//打印双引号包围的string
string debug_rep(const string& s)
{return '"' + s + '"';
}int main()
{string s("hi");cout << debug_rep(s) << endl;
}

对于一个调用,如果一个非函数模板与一个函数模板提供同样好的匹配,则选择非模板版本。

重载模板和类型转换

还有一种情况我们到目前为止尚未讨论:C风格字符串指针和字符串字面常量。

现在有了一个接受string的debug_rep版本,我们可能期望一个传递字符串的调用会匹配这个版本。但是,考虑这个调用:
 

cout << debug_rep("hi world!") << endl; // 调用debug_rep(T*)

本例中所有三个debug rep版本都是可行的:

  1. debug rep(const T&),T被绑定到char[10]。
  2. debug rep(T*),T被绑定到const char。
  3. debug rep(const string&),要求从const char*到string的类型转换。

对给定实参来说,两个模板都提供精确匹配——第二个模板需要进行一次(许可的)数组到指针的转换,而对于函数匹配来说,这种转换被认为是精确匹配。

非模板版本是可行的,但需要进行一次用户定义的类型转换,因此它没有精确匹配那么好,所以两个模板成为可能调用的函数。

与之前一样,T*版本更加特例化,编译器会选择它。

#include<iostream>
#include<sstream>
using namespace std;
//打印任何我们不能处理的类型
template <typename T> 
string debug_rep(const T& t)
{cout << "使用了const T&t版本" << endl;ostringstream ret;ret << t; // 使用T的输出运算符打印t的一个表示形式return ret.str();// 返回ret 绑定的string的一个副本
}template <typename T> 
string debug_rep(T* p)
{cout << "使用了T*p版本" << endl;ostringstream ret;  // 打印指针本身的值ret << "pointer: " << p;if (p)ret << "" << debug_rep(*p);// 打印p指向的值elseret << " null pointer"; // 或指出ρ为空return ret.str();// 返回ret 绑定的string的一个副本
}//打印双引号包围的string
string debug_rep(const string& s)
{return '"' + s + '"';
}int main()
{cout << debug_rep("hi world!") << endl; // 调用debug_rep(T*)
}

如果我们希望将字符指针按string处理,可以定义另外两个非模板重载版本:

//将字符指针转换为string,并调用string版本的debug_reg
string debug_rep(char* p)
{return debug_rep(string(p));
}
string debug_rep(const char* p)
{return debug_rep(string(p));
}

缺少声明可能导致程序行为异常

值得注意的是,为了使 char*版本的 debug_rep 正确工作,在定义此版本时,debug_rep (const string)的声明必须在作用域中。否则,就可能调用错误的debug_rep版本:

template <typename T> string debug_rep(const T& t);
template<typename T> string debug_rep(T* p);// 为了使debug_rep(char*)的定义正确工作,下面的声明必须在作用域中
string debug_rep(const string&);string debug_rep(char* p)
{// 如果接受一个const string&的版本的声明不在作用域中,// 返回语句将调用 debug_rep(const T&)的T实例化为string的版本return debug_rep(string(p));
}


通常,如果使用了一个忘记声明的函数,代码将编译失败。

但对于重载函数模板的函数而言,则不是这样。

如果编译器可以从模板实例化出与调用匹配的版本,则缺少的声明就不重要了。

在本例中,如果忘记了声明接受string参数的debug_rep版本,编译器会默默地实例化接受const T&的模板版本。

#include<iostream>
#include<sstream>
using namespace std;
//打印任何我们不能处理的类型
template <typename T> 
string debug_rep(const T& t)
{cout << "使用了const T&t版本" << endl;ostringstream ret;ret << t; // 使用T的输出运算符打印t的一个表示形式return ret.str();// 返回ret 绑定的string的一个副本
}template <typename T> 
string debug_rep(T* p)
{cout << "使用了T*p版本" << endl;ostringstream ret;  // 打印指针本身的值ret << "pointer: " << p;if (p)ret << "" << debug_rep(*p);// 打印p指向的值elseret << " null pointer"; // 或指出ρ为空return ret.str();// 返回ret 绑定的string的一个副本
}string debug_rep(char* p)
{
// 如果接受一个const string&的版本的声明不在作用域中,
// 返回语句将调用 debug_rep(const T&)的T实例化为string的版本
return debug_rep(string(p));
}int main()
{cout << debug_rep("hi world!") << endl; // 调用debug_rep(T*)
}

 

在定义任何函数之前,记得声明所有重载的函数版本。这样就不必担心编译器T由于未遇到你希望调用的函数而实例化一个并非你所需的版本。
 

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

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

相关文章

SAP:无法在插件模式 HTTP 中处理消息 E ** xxx

问题描述&#xff1a;利用post方式接口&#xff0c;返回信息为 无法在插件模式 HTTP 中处理消息 E ** xxx ,如何排查是因为什么问题导致的&#xff1f; 解决方法&#xff1a; 事务码&#xff1a;SE91, 输入消息类&#xff0c;消息编号&#xff0c;点击显示&#xff0c;查看报…

Sketch webView方式插件开发技术总结

Sketch作为一款广受欢迎的矢量图形设计工具&#xff0c;其功能远不止基础的矢量设计&#xff0c;它的真正实力部分源自其丰富的插件生态系统。Sketch向开发者提供了官方的第三方插件接口&#xff0c;这使得整个社区能够创建和分享众多功能各异的插件&#xff0c;极大地拓展了Sk…

vue3+ts 调用接口,数据显示

数据展示 &#xff08;例&#xff1a;展示医院等级数据&#xff0c;展示医院区域数据同理。&#xff09; 接口文档中&#xff0c;输入参数 测试一下接口&#xff0c;发请求 看是否能够拿到信息 获取接口&#xff0c;api/index.ts 中 /home/index.ts // 统一管理首页模块接口 i…

突破校园网限速:使用 iKuai 多拨分流负载均衡 + Clash 代理(内网带宽限制通用)

文章目录 1. 简介2. iKuai 部署2.1 安装 VMware2.2 安装 iKuai(1) 下载固件(2) 安装 iKuai 虚拟机(3) 配置 iKuai 虚拟机(4) 配置 iKuai(5) 配置多拨分流 2.3 测试速度 3. Clash 部署3.1 准备工作(1) 配置磁盘分区(2) 安装 Docker(3) 安装 Clash(4) 设置代理 1. 简介 由于博主…

JavaScript(六)---【回调、异步、promise、Async】

零.前言 JavaScript(一)---【js的两种导入方式、全局作用域、函数作用域、块作用域】-CSDN博客 JavaScript(二)---【js数组、js对象、this指针】-CSDN博客 JavaScript(三)---【this指针&#xff0c;函数定义、Call、Apply、函数绑定、闭包】-CSDN博客 JavaScript(四)---【执…

Makefile:调用shell脚本和嵌套调用多项目编译(九)

1、Makefile中调用shell脚本 Makefile中可以通过使用$(shell 指令)的方式调用shell脚本a指令&#xff1a;输出当前文件夹下的所有文件b指令&#xff1a;输出当前路径c指令&#xff1a;如果当前目录下不存在abc文件那么创建一个abc的文件 a$(shell ls ./) b$(shell pwd) filen…

MySQL 学习心得和知识总结(五)|MySQL的一般查询日志(general log)

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…

LLM:检索增强生成(RAG)

1 Embedding技术 简单地说&#xff0c;嵌入(Embedding)思想可以视为一种尝试通过用向量来表示所有东西的“本质”的方法&#xff0c;其特性是“相近的事物”由相近的数表示。 1.1 文本向量(Text Embedding) 在GPT中&#xff0c;文本嵌入(Text Embedding)是通过将输入文本中的每…

搭建跨境电商电商独立站如何接入1688平台API接口|通过1688API接口采集商品通过链接搜索商品下单

接口设计|接口接入 对于mall项目中商品模块的接口设计&#xff0c;大家可以参考项目的Swagger接口文档&#xff0c;以Pms开头的接口就是商品模块对应的接口。 参数说明 通用参数说明 参数不要乱传&#xff0c;否则不管成功失败都会扣费url说明……d.cn/平台/API类型/ 平台&…

WEB 工程路径

WEB 工程路径 相对路径 使用相对路径来解决&#xff0c; 一个非常重要的规则&#xff1a;页面所有的相对路径&#xff0c;在默认情况下&#xff0c;都会参考当前浏览器地址栏的路径 http://ip:port/工程名/ 资源来进行跳转。 相对路径带来的问题 如上图&#xff0c;若在a.h…

黄锈水过滤器 卫生热水工业循环水色度水处理器厂家工作原理动画

​ 1&#xff1a;黄锈水处理器介绍 黄锈水处理器是一种专门用于处理“黄锈水”的设备&#xff0c;它采用机电一体化设计&#xff0c;安装方便&#xff0c;操作简单&#xff0c;且运行费用极低。这种处理器主要由数码射频发生器、射频换能器、活性过滤体三部分组成&#xff0c;…

MySQL的基本操作(超详细)

&#x1f468;‍&#x1f4bb;作者简介&#xff1a;&#x1f468;&#x1f3fb;‍&#x1f393;告别&#xff0c;今天 &#x1f4d4;高质量专栏 &#xff1a;☕java趣味之旅 &#x1f4d4;&#xff08;零基础&#xff09;专栏&#xff1a;MSQL数据库 欢迎&#x1f64f;点赞&…