C++ 模板的显示具体化

2023-11-16

C++ 没有办法限制类型参数的范围,我们可以使用任意一种类型来实例化模板。但是模板中的语句(函数体或者类体)不一定就能适应所有的类型,可能会有个别的类型没有意义,或者会导致语法错误。


例如有下面的函数模板,它用来获取两个变量中较大的一个:
template<class T> const T& Max(const T& a, const T& b){
    return a > b ? a : b;
}
请读者注意a > b这条语句,>能够用来比较 int、float、char 等基本类型数据的大小,但是却不能用来比较结构体变量、对象以及数组的大小,因为我们并没有针对结构体、类和数组重载>。


另外,该函数模板虽然可以用于指针,但比较的是地址大小,而不是指针指向的数据,所以也没有现实的意义。


除了>,+-*/==<等运算符也只能用于基本类型,不能用于结构体、类、数组等复杂类型。总之,编写的函数模板很可能无法处理某些类型,我们必须对这些类型进行单独处理。


模板是一种泛型技术,它能接受的类型是宽泛的、没有限制的,并且对这些类型使用的算法都是一样的(函数体或类体一样)。但是现在我们希望改变这种“游戏规则”,让模板能够针对某种具体的类型使用不同的算法(函数体或类体不同),这在 C++ 中是可以做到的,这种技术称为模板的显示具体化(Explicit Specialization)。


函数模板和类模板都可以显示具体化,下面我们先讲解函数模板的显示具体化,再讲解类模板的显示具体化。
函数模板的显示具体化


在讲解函数模板的显示具体化语法之前,我们先来看一个显示具体化的例子:
#include <iostream>
#include <string>
using namespace std;
typedef struct{
    string name;
    int age;
    float score;
} STU;
//函数模板
template<class T> const T& Max(const T& a, const T& b);
//函数模板的显示具体化(针对STU类型的显示具体化)
template<> const STU& Max<STU>(const STU& a, const STU& b);
//重载<<
ostream & operator<<(ostream &out, const STU &stu);
int main(){
    int a = 10;
    int b = 20;
    cout<<Max(a, b)<<endl;
   
    STU stu1 = { "王明", 16, 95.5};
    STU stu2 = { "徐亮", 17, 90.0};
    cout<<Max(stu1, stu2)<<endl;
    return 0;
}
template<class T> const T& Max(const T& a, const T& b){
    return a > b ? a : b;
}
template<> const STU& Max<STU>(const STU& a, const STU& b){
    return a.score > b.score ? a : b;
}
ostream & operator<<(ostream &out, const STU &stu){
    out<<stu.name<<" , "<<stu.age <<" , "<<stu.score;
    return out;
}
运行结果:
20
王明 , 16 , 95.5


本例中,STU 结构体用来表示一名学生(Student),它有三个成员,分别是姓名(name)、年龄(age)、成绩(score);Max() 函数用来获取两份数据中较大的一份。


要想获取两份数据中较大的一份,必然会涉及到对两份数据的比较。对于 int、float、char 等基本类型的数据,直接比较它们本身的值即可,而对于 STU 类型的数据,直接比较它们本身的值不但会有语法错误,而且毫无意义,这就要求我们设计一套不同的比较方案,从语法和逻辑上都能行得通,所以本例中我们比较的是两名学生的成绩(score)。


不同的比较方案最终导致了算法(函数体)的不同,我们不得不借助模板的显示具体化技术对 STU 类型进行单独处理。第 14 行代码就是显示具体化的声明,第 34 行代码进行了定义。


请读者注意第 34 行代码,Max<STU>中的STU表明了要将类型参数 T 具体化为 STU 类型,原来使用 T 的位置都应该使用 STU 替换,包括返回值类型、形参类型、局部变量的类型。


Max 只有一个类型参数 T,并且已经被具体化为 STU 了,这样整个模板就不再有类型参数了,类型参数列表也就为空了,所以模板头应该写作template<>。


另外,Max<STU>中的STU是可选的,因为函数的形参已经表明,这是 STU 类型的一个具体化,编译器能够逆推出 T 的具体类型。简写后的函数声明为:
template<> const STU& Max(const STU& a, const STU& b);
函数的调用规则


回顾一下前面学习到的知识,在 C++ 中,对于给定的函数名,可以有非模板函数、模板函数、显示具体化模板函数以及它们的重载版本,在调用函数时,显示具体化优先于常规模板,而非模板函数优先于显示具体化和常规模板。
类模板的显示具体化


除了函数模板,类模板也可以显示具体化,并且它们的语法是类似的。


在《C++类模板》一节中我们定义了一个 Point 类,用来输出不同类型的坐标。在输出结果中,横坐标 x 和纵坐标 y 是以逗号,为分隔的,但是由于个人审美的不同,我希望当 x 和 y 都是字符串时以|为分隔,是数字或者其中一个是数字时才以逗号,为分隔。为了满足我这种奇葩的要求,可以使用显示具体化技术对字符串类型的坐标做特殊处理。


下面的例子演示了如何对 Point 类进行显示具体化:
#include <iostream>
using namespace std;
//类模板
template<class T1, class T2> class Point{
public:
    Point(T1 x, T2 y): m_x(x), m_y(y){ }
public:
    T1 getX() const{ return m_x; }
    void setX(T1 x){ m_x = x; }
    T2 getY() const{ return m_y; }
    void setY(T2 y){ m_y = y; }
    void display() const;
private:
    T1 m_x;
    T2 m_y;
};
template<class T1, class T2>  //这里要带上模板头
void Point<T1, T2>::display() const{
    cout<<"x="<<m_x<<", y="<<m_y<<endl;
}
//类模板的显示具体化(针对字符串类型的显示具体化)
template<> class Point<char*, char*>{
public:
    Point(char *x, char *y): m_x(x), m_y(y){ }
public:
    char *getX() const{ return m_x; }
    void setX(char *x){ m_x = x; }
    char *getY() const{ return m_y; }
    void setY(char *y){ m_y = y; }
    void display() const;
private:
    char *m_x;  //x坐标
    char *m_y;  //y坐标
};
//这里不能带模板头template<>
void Point<char*, char*>::display() const{
    cout<<"x="<<m_x<<" | y="<<m_y<<endl;
}
int main(){
    ( new Point<int, int>(10, 20) ) -> display();
    ( new Point<int, char*>(10, "东京180度") ) -> display();
    ( new Point<char*, char*>("东京180度", "北纬210度") ) -> display();
    return 0;
}
运行结果:
x=10, y=20
x=10, y=东京180度
x=东京180度 | y=北纬210度


请读者注意第 25 行代码,Point<char*, char*>表明了要将类型参数 T1、T2 都具体化为char*类型,原来使用 T1、T2 的位置都应该使用char*替换。Point 类有两个类型参数 T1、T2,并且都已经被具体化了,所以整个类模板就不再有类型参数了,模板头应该写作template<>。


再来对比第 19、40 行代码,可以发现,当在类的外部定义成员函数时,普通类模板的成员函数前面要带上模板头,而具体化的类模板的成员函数前面不能带模板头。
部分显式具体化


在上面的显式具体化例子中,我们为所有的类型参数都提供了实参,所以最后的模板头为空,也即template<>。另外 C++ 还允许只为一部分类型参数提供实参,这称为部分显式具体化。


部分显式具体化只能用于类模板,不能用于函数模板。


仍然以 Point 为例,假设我现在希望“只要横坐标 x 是字符串类型”就以|来分隔输出结果,而不管纵坐标 y 是什么类型,这种要求就可以使用部分显式具体化技术来满足。请看下面的代码:
#include <iostream>
using namespace std;
//类模板
template<class T1, class T2> class Point{
public:
    Point(T1 x, T2 y): m_x(x), m_y(y){ }
public:
    T1 getX() const{ return m_x; }
    void setX(T1 x){ m_x = x; }
    T2 getY() const{ return m_y; }
    void setY(T2 y){ m_y = y; }
    void display() const;
private:
    T1 m_x;
    T2 m_y;
};
template<class T1, class T2>  //这里需要带上模板头
void Point<T1, T2>::display() const{
    cout<<"x="<<m_x<<", y="<<m_y<<endl;
}
//类模板的部分显示具体化
template<typename T2> class Point<char*, T2>{
public:
    Point(char *x, T2 y): m_x(x), m_y(y){ }
public:
    char *getX() const{ return m_x; }
    void setX(char *x){ m_x = x; }
    T2 getY() const{ return m_y; }
    void setY(T2 y){ m_y = y; }
    void display() const;
private:
    char *m_x;  //x坐标
    T2 m_y;  //y坐标
};
template<typename T2>  //这里需要带上模板头
void Point<char*, T2>::display() const{
    cout<<"x="<<m_x<<" | y="<<m_y<<endl;
}
int main(){
    ( new Point<int, int>(10, 20) ) -> display();
    ( new Point<char*, int>("东京180度", 10) ) -> display();
    ( new Point<char*, char*>("东京180度", "北纬210度") ) -> display();
    return 0;
}
运行结果:
x=10, y=20
x=东京180度 | y=10
x=东京180度 | y=北纬210度


本例中,T1 对应横坐标 x 的类型,我们将 T1 具体化为char*,第 25 行代码就是类模板的部分显示具体化。


模板头template<typename T2>中声明的是没有被具体化的类型参数;类名Point<char*, T2>列出了所有类型参数,包括未被具体化的和已经被具体化的。


类名后面之所以要列出所有的类型参数,是为了让编译器确认“到底是第几个类型参数被具体化了”,如果写作template<typename T2> class Point<char*>,编译器就不知道char*代表的是第一个类型参数,还是第二个类型参数。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ 模板的显示具体化 的相关文章

  • json.net自定义jobject反序列化

    我正在尝试使用 JsonConvert DeserializeObject string 将字符串反序列化为可与动态一起使用的 jobject 来动态访问 json 文档 但是我想避免知道文档的大小写 以便我可以输入 dynamic doc
  • 您可以从基本 Win32 控制台模板应用程序中的 C#/Winrt 组件调用(不是 WinForm/abstractions/wrappers 或使用 C++/Winrt 模板)吗?)

    我有一个现有的程序 win32 x86 控制台应用程序 需要调用托管代码 来自 Net 的 C dll The dll不暴露给 COM 但可以从 C WinRT 组件调用并由 C WinRT 控制台模板应用引用 BUT即使安装了 C Win
  • 如何在另一个应用程序中挂钩 api 调用

    我正在尝试挂钩另一个应用程序的 ExtTextOut 和 DrawTextExt GDI 方法调用 我知道我需要使用 GetProcAddress 来查找 gdi32 dll 中那些方法的地址 并用我的函数的地址覆盖我想要挂钩的进程中的地址
  • 在 OnModelCreating 期间设置列名称

    Issue 我目前正在尝试通过设置的属性为我的表及其列添加前缀 我正在使用实体框架核心 我已经正确地为表名添加了前缀 但我似乎无法弄清楚列的前缀 我有一种感觉 我需要使用反射 我已经留下了我的 可能很糟糕的 反思尝试 有人有办法在实体中设置
  • C++ 长 switch 语句还是用地图查找?

    在我的 C 应用程序中 我有一些值充当代表其他值的代码 为了翻译代码 我一直在争论使用 switch 语句还是 stl 映射 开关看起来像这样 int code int value switch code case 1 value 10 b
  • Gwan C#,如何获取HTTP标头?

    我需要它来重写 url 以了解我正在处理哪个友好的 url 用于用户代理和其他东西 EDIT public class Gwan MethodImplAttribute MethodImplOptions InternalCall exte
  • C# 5 async/await 线程机制感觉不对?

    为什么让调用线程进入异步方法直到内部 等待 一旦调用异步方法就生成一个线程 这不是更干净吗 这样您就可以确定异步方法会立即返回 您不必担心在异步方法的早期阶段没有做任何昂贵的事情 我倾向于知道某个方法是否要在 我的 线程上执行代码 不管是堵
  • C# 开源 NMEA 解析器 [已关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在寻找 C 开源 NMEA 解析器 嗯 我自己也不熟悉 但是一些快速搜索显示了一个代码项目 htt
  • 使用查询表达式对 List 进行排序

    我在使用 Linq 订购这样的结构时遇到问题 public class Person public int ID get set public List
  • MFC:如何设置CEdit框的焦点?

    我正在开发我的第一个简单的 MFC 项目 但我正在努力解决一个问题 想要设置所有的焦点CEdit其中一个对话框中的框 我的想法是 当打开对话框时 焦点位于第一个编辑框上 然后使用 选项卡 在它们之间交换 我看到了方法SetFocus 但我无
  • 将接口转换为其具体实现对象,反之亦然?

    在 C 中 当我有一个接口和几个具体实现时 我可以将接口强制转换为具体类型 还是将具体类型强制转换为接口 这种情况下的规则是什么 Java 和 C 中都允许这两个方向 向下转型需要显式转型 如果对象类型不正确 可能会抛出异常 然而 向上转换
  • 使用 C# 和 wpf 创建类似 Dock 的应用程序

    我需要创建一个与我们购买笔记本电脑时获得的应用程序类似的应用程序 仅当鼠标指针到达窗口顶部时它才可见 那么我怎样才能使用 C 4 0 来做到这一点呢 http www notebookcheck net uploads pics win2
  • 在 asp.net MVC 中使用活动目录进行身份验证

    我想使用活动目录对我的 asp net mvc 项目中的用户进行身份验证 在网上冲浪了几个小时后 我没有找到任何对我有用的东西 我已经看到了所有结果 但什么也没有 我尝试按照许多帖子的建议编辑我的 web config 如果有人可以帮助我提
  • Project Euler #8,我不明白我哪里出了问题[关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 我正在做项目欧拉第八题 https projecteuler net problem 8 其中我得到了这个大得离谱的数字 7316
  • 英文日期差异

    接近重复 如何计算相对时间 https stackoverflow com questions 11 how do i calculate relative time 如何在 C 中计算某人的年龄 https stackoverflow c
  • 从浏览器访问本地文件?

    您好 我想从浏览器访问系统的本地文件 由于涉及大量安全检查 是否可以通过某种方式实现这一目标 或使用 ActiveX 或 Java Applet 的任何其他工作环境 请帮帮我 要通过浏览器访问本地文件 您可以使用签名的 Java Apple
  • 如何在 winforms 应用程序的主屏幕显示之前显示欢迎屏幕?

    我想在应用程序启动时加载欢迎屏幕 然后用户单击欢迎屏幕上的按钮 然后关闭欢迎屏幕 最后显示主屏幕 static void Main startup method being called Application EnableVisualSt
  • C++ 中 void(*)() 和 void(&)() 之间的区别[重复]

    这个问题在这里已经有答案了 在此示例代码中 func1是类型void int double and funky是类型void int double include
  • DataContractSerializer 事件/委托字段问题

    在我的 WPF 应用程序中 我正在使用DataContractSerializer序列化对象 我发现它无法序列化具有事件或委托声明的类型 考虑以下失败的代码 Serializable public abstract class BaseCl
  • 如何使用placement new重新初始化该字段?

    我的课程包含字段 private OrderUpdate curOrderUpdate 我一遍又一遍地使用它 经常需要重新初始化 for int i 0 i lt entries size i auto entry entries i ne

随机推荐

  • 如何关闭rabbitmq

    rabbitmqctl stop 方式2 先用ps ef grep rabbitmq 查询出进程号 然后用kill 9 进程号 杀死进程 RabbitMQ常用命令 说明 命令 启用Web控制台 rabbitmq plugins enable
  • 路由器ipv6怎么设置才能上网_设置路由器时,如何正确选择上网方式?

    设置路由器时 常见有自动获得IP地址 宽带拨号上网 固定IP地址三种上网方式 选择任意一个 均可进入下一步 但上网方式选择错误 即使设置步骤完成 也不能上网 那么设置路由器时 如何正确选择上网方式呢 取决于当前宽带线路的上网方式 本文以Wi
  • NoSQL -- 1.NoSQL与TRDB的区别

    TRDB TRDB Traditional Relational Database 即 传统关系型数据库 关系型数据库 是指采用了关系模型来组织数据的数据库 其以行和列的形式存储数据 以便于用户理解 关系型数据库这一系列的行和列被称为表 一
  • 将一个对象的属性值赋值给另一个对象

    java 对象属性复制 将一个对象的属性值赋值给另一个对象 属性名需要相同 import org springframework beans BeanUtils BeanUtils copyProperties 源对象 目标对象
  • 一位老学长的真实互联网校招求职心路历程~

    自我介绍 听说很多家公司2019年的春招已经陆续开始了 作为一个备战过2018年春招和秋招的求职狗 想来聊一下自己的校招求职经历 我本科是哈尔滨的一所211大学 万年老二 学的是电子信息工程 由于当年高考发挥失常 自己又不想复读 所以从入学
  • Java+GeoTools(开源的Java GIS工具包)快速入门-实现读取shp文件并显示

    场景 GeoTools GeoTools 是一个开源的 Java GIS 工具包 可利用它来开发符合标准的地理信息系统 GeoTools 提供了 OGC Open Geospatial Consortium 规范的一个实现来作为他们的开发
  • SLAM--三角测量SVD分解法、最小二乘法及R t矩阵的判断

    目录 一 三角测量 方法一 SVD分解法的推导 方法二 最小二乘法求解 二 ORB SLAM2 三角测量源码 三 利用Eigen源码实现三角测量 方法一 SVD分解法 方法二 最小二乘法求解 速度最快 方法三 利用OpenCV自带函数 四
  • go语言面试题:令牌桶算法原理

    令牌桶算法是一种常见的限流算法 它基于一个简单的思想 系统中的请求像一个桶 在请求发送之前都需要从桶中获取一个令牌 如果桶里没有可用的令牌 则该请求会被暂时禁止 具体来说 令牌桶算法会在桶内放入固定数量的令牌 这些令牌以固定的速率自动回复
  • 二阶系统响应指标图_一阶和二阶系统的动态特性参数

    检测系统的时域动态性能指标一般都是用阶跃输入时检测系统的输出响应 即过渡过程曲线上的特性参数来表示 1 一阶系统的时域动态特性参数 一阶测量系统时域动态特性参数主要是时间常数及与之相关的输出响应时间 1 时间常数 时间常数是一阶系统的最重要
  • 三十一.刷题.20

    统计给定的n个数中 负数 零和正数的个数 include
  • LeetCode 1309. 解码字母到整数映射

    给你一个字符串 s 它由数字 0 9 和 组成 我们希望按下述规则将 s 映射为一些小写英文字符 字符 a i 分别用 1 9 表示 字符 j z 分别用 10 26 表示 返回映射之后形成的新字符串 题目数据保证映射始终唯一 示例 1 输
  • 练习题:猜年龄游戏升级版

    猜年龄游戏升级版 需求 1 允许用户最多尝试3次 2 每尝试3次后 如果还没猜对 就问用户是否还想继续玩 如果回答Y或y 就继续让其猜3次 以此往复 如果回答N或n 就退出程序 3 如何猜对了 就直接退出 import random n r
  • mysql基础--存储过程

    文章目录 MySQL存储过程 1 创建存储过程 2 调用存储过程 3 变量定义 3 1 局部变量 3 2 用户变量 3 3 系统变量 3 3 1 系统变量 全局变量 3 3 2 系统变量 会话变量 4 存储过程传参 4 1 in 4 2 o
  • Unity进阶--物品,背包,角色管理器

    文章目录 物品管理器 背包管理器 角色管理器 物品管理器 物品数据 Item json json部分 Resources Data Item id 1 name 新手剑 des 这是一把宝剑 price 200 icon attack 10
  • C语言:通过函数指针来完成两个数的加减乘除(函数指针当做参数使用)

    main c Function pointer Created by mac on 15 8 2 Copyright c 2015年 All rights reserved 要求 将函数指针做参数来求两个整数的和 差 积 商 知识点 函数指
  • 【深度学习与计算机视觉】10、深度学习框架Tensorflow

    Tensorflow
  • python做的多激光雷达外参标定程序(初版本 完整版见专栏)

    查阅了一番资料和现有的代码后发现 现在的多个激光雷达之间的标定程序都是ROS框架下面的 并且都是C 代码 需要安装的依赖也比较复杂 于是自己写了一个python版本的标定程序 依赖非常简单 Windows系统也可以运行 并且代码简单 扩展性
  • 网络安全—DDOS和CC攻击的区别

    DDOS和CC攻击的区别 1 攻击简介 DDOS 分布式拒绝服务攻击 通过向目标发送大量数据包 耗尽其带宽 来使目标无法可用 CC攻击 DDOS的一种 也可以理解为应用层DDOS攻击 利用大量代理服务器对目标计算机发起大量连接 导致目标服务
  • 华中师范大学2018年874

    lt 对数组A的N个整数从小到大进行连续编号 输出各个元素的编号 例如对数组 A 5 3 4 7 3 5 6 则输出为 3 1 2 5 1 3 4 include
  • C++ 模板的显示具体化

    C 没有办法限制类型参数的范围 我们可以使用任意一种类型来实例化模板 但是模板中的语句 函数体或者类体 不一定就能适应所有的类型 可能会有个别的类型没有意义 或者会导致语法错误 例如有下面的函数模板 它用来获取两个变量中较大的一个 temp