C++模板之模板成员函数不能偏特化

目录

1.引言

2.类模板成员函数的特化

2.1.没有函数特化的类模板

2.2.增加函数特化        

3.“曲线救国”函数“偏特化”

3.1.函数重载实现“偏特化”

3.2.使用类型选择机制实现“偏特化”

4.总结


1.引言

        C++ 泛型编程的资料在介绍类模板的特化和偏特化的时候,都会提到“虽然类模板可以偏特化,但是类模板的成员函数不可以偏特化”。对于初次接触泛型编程的 C++ 程序员来说,甚至对 C++ 有一定了解的人,一时也会摸不着头脑。到底模板成员函数的特化和偏特化是怎么回事儿,类模板成员函数不能偏特化是什么意思?本文就用几个简单的例子解释一下这句话。

2.类模板成员函数的特化

2.1.没有函数特化的类模板

        当你开始了解 C++ 的泛型编程的时候,类模板的特化和偏特化就是两个绕不开的话题。在很多 C++ 的书籍中都会提到一点,就是:“虽然类模板可以偏特化,但是类模板的成员函数不可以偏特化”。这句话到底是什么意思呢?本篇小文就用一段例子代码,直观地解释一下这句话。读懂本篇小文,你需要对 C++ 的类模板、类模板的特化相关的概念和 C++ 的语法实现有基本的了解。
        废话少说,我们先定义一个类模板 SpecialClass<> ,它有两个模板参数,分别是 U 和 T。这个类模板有两个(函数)方法,即 FuncA() 和 FuncB(),如下代码片段所示:

        对于这个类模板,我们可以这样实例化它们,下面的代码片段中,sp1 和 sp2 就是类模板的两个实例化对象:

        不出意料,两个类模板的实例对象的函数调用的输出结果都是主模板(Primary Template)类的 FuncA() 和 FuncB() 的输出:

SpecialClass::FuncA(U, T) invoked!
SpecialClass::FuncB(U, T) invoked!
SpecialClass::FuncA(U, T) invoked!
SpecialClass::FuncB(U, T) invoked!

2.2.增加函数特化        

        既然标准已经说了,类模板不支持成员函数的偏特化,那么我们先来个全特化成员函数,看看全特化成员函数是什么情况。我们对 FuncA() 进行全特化,保留 FuncB() 作为对比:

        我们用 int 和 char 对 FuncA() 进行特化,然后再像代码 2 那样创建两个类模板的实例,此时运行的结果如下所示,第二个实例中的 FuncA() 特化起了作用,但是 FuncB()仍然是主模板类的函数:

SpecialClass::FuncA(U, T) invoked!
SpecialClass::FuncB(U, T) invoked!
SpecialClass::FuncA(int, char) invoked!
SpecialClass::FuncB(U, T) invoked!

        正如标准所说的那样,类模板中函数的全特化是没有问题的,那么我们现在来看看函数的偏特化。如下代码片段所示,我们只特化模板参数 T:

        终于,编译器拒绝编译这段代码,不同的编译器可能给出的错误信息不一样。我用的 gcc 7.3 抱怨说 FuncA 的类型 class SpecialClass<U, char> 声明不完整,Visual C++ 14.0(Visual Studio 2015) 给出的错误信息是SpecialClass<U,char>::FuncA 无法匹配已经存在的函数声明:void SpecialClass<U,T>::FuncA(U,T) 。编译器的提示信息和大多数涉及模板的编译错误提示信息一样,一如既往的含蓄和隐晦。
        至此,我们已经用具体的例子解释了类模板成员函数的特化和偏特化(不被允许),实际上,不仅类模板的成员函数不支持偏特化,有具体的名字域(namespace)级别的非成员函数,也不支持偏特化。如果我们不死心,一定要 FuncA 的第二个参数使用 char 类型怎么办呢?方法当然有,那就是利用 C++ 语言的函数重载机制“曲线救国”。

3.“曲线救国”函数“偏特化”

        注意,这里要介绍的两个方法都不是真正意义上的函数偏特化,因为 C++ 不支持模板函数偏特化,所以标题都加了引号。这里介绍的两种方法都是采用模拟的方法,让编译器能够根据函数参数的差异实现函数的最佳匹配。

3.1.函数重载实现“偏特化”

        假如我们无论如何都要给 SpecialClass<> 增加一个 FuncA 的定制版本: FuncA(U para1, char para2),使得这个函数对 char 类型参数时,能够使用更高效的算法实现。简单一点的方法就是利用函数重载,直接给 SpecialClass<> 增加一个重载函数,比如这样:

template <class U, class T>
class TestClass
{
public:void FuncA(U para1, T para2){std::cout << "TestClass::FuncA(U, T) invoked!" << std::endl;}void FuncA(U para1, char para2){std::cout << "TestClass::FuncA(U, char) invoked!" << std::endl;}
};//模板实例化应用
TestClass<std::string, double> tp1;tp1.FuncA("dsdfsd", 23.1); //第一个重载函数被调用
tp1.FuncA("dsdfsd", 'B'); //第二个重载函数被调用

3.2.使用类型选择机制实现“偏特化”

C++ 的 Tag Dispatching(标签派发) 惯用法-CSDN博客

        《Modern C++ Design》这本书里介绍了一种利用编译期间类型选择机制实现的“函数”的偏特化选择,这种技术被称为“Tag Dispatching”,其本质仍然是利用了 C++ 的函数重载机制,这里我们就简单介绍一下这种技术。
        首先介绍一种 C++ 泛型编程种常用的技巧,即“类型对类型的映射”。C++ 是一种强类型语言,对于类模板的实例来说,SomeClass<int> 和 SomeClass<double> 是两种不同的类型(虽然它们的代码是一套代码)。利用这个特点,我们可以定义一个类型到类型映射的模板类:

template <typename T>
struct Type2Type
{typedef T thisNewType;
};

        这里我想插入解答一个读者经常问我的问题,即:为什么你这里用 struct 而不是 class?其实不只是我,很多人都这么用,原因很简单,就是省事儿,你接着往下看就明白了。这样在获取映射类型的时候,就可以直接这么用:

Type2Type<T>::thisNewType 是个类型定义,可被用于编译器的自动推导Type2Type<int>::thisNewType a; // 等同于: int a;

        当然可以用 class ,不过别忘了,C++ 的 class 与 struct 的区别之一就是 class 的属性默认是私有的,如果用 class,代码要这么写:

template <typename T>
class Type2Type
{
public:typedef T thisNewType;
};

        好了,现在言归正传。我们已经知道 Type2Type<int> 和 Type2Type<double> 是两种完全不同的类型,如果将其作为函数的参数,就可以利用函数的重载机制,让编译器区分它们,既能够根据类型自动生成对应类型的函数实例,又能引导编译器将某些特定类型参数的函数调用链接到针对特定类型实现的函数体上。

template <class U, class T>
T* CreateObject(U para1, Type2Type<T>)
{std::cout << "SpecialTypeClass::CreateObject(U, T) invoked!" << std::endl;return new T(para1); //毫无意义的代码
}template <class U>
char* CreateObject(U para1, Type2Type<char>)
{std::cout << "SpecialTypeClass::CreateObject(U, char) invoked!" << std::endl;return new char(para1); //又一行毫无意义的代码
}

       使用起来也很简单,第二个参数仅仅是用于告诉编译器如何根据类型匹配正确的函数体:

double *pValue = CreateObject(5, Type2Type<double>());char *pCh = CreateObject(5, Type2Type<char>());

       没有悬念,程序输出如我们所愿:

SpecialTypeClass::CreateObject(U, T) invoked!
SpecialTypeClass::CreateObject(U, char) invoked!

        使用这种方法,还有一个好处,就是简化 CreateObject() 函数的使用。想想在 C++11 标准出来之前,为了让编译器能正确实作出对应模板参数的函数,我们不得不这样写代码:

//这样定义模板函数
template <class U, class T>
T* CreateObject(U para1)
{ std::cout << "SpecialTypeClass::CreateObject(U, T) invoked!" << std::endl; return new T(para1); //毫无意义的代码
}//这样使用模板函数
double *pValue = CreateObject<int, double>(5);

4.总结

        好了,本篇小文就写到这里,总结一下内容,首先实例代码讲解了很多 C++ 书籍都提到,但是没有细说的:“虽然类模板可以偏特化,但是类模板的成员函数不可以偏特化” 这句话的意思,然后又介绍了两种利用函数重载机制实现的变相函数“偏特化”的方法。

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

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

相关文章

【HarmonyOS】HUAWEI DevEco Studio 下载地址汇总

目录 OpenHarmony 4.x Releases 4.1 Release4.0 Release OpenHarmony 3.x Releases 3.2.1 Release3.2 Release3.1.3 Release3.1.2 Release3.1.1 Release3.1 Release 说明 Full SDK&#xff1a;面向OEM厂商提供&#xff0c;包含了需要使用系统权限的系统接口。 Public SDK&am…

Python对Excel表格的操作

今天, 实现了一个对excel表格操作的技术方案. 操作的要求是: (1)在一个目标表格(表格2)中的第2列已经有唯一标识码.第1列为凭证号, 但是是空的. (2)在数据表格中(表格1)中有资产的信息, 其中第2列是资产的唯一标识码, 第1列是凭证号. (3)表格2内只有部分资产. 要求: 从表格1中…

前端:鼠标点击实现高亮特效

一、实现思路 获取鼠标点击位置 通过鼠标点击位置设置高亮裁剪动画 二、效果展示 三、按钮组件代码 <template><buttonclass"blueBut"click"clickHandler":style"{backgroundColor: clickBut ? rgb(31, 67, 117) : rgb(128, 128, 128),…

C# OpenCvSharp 图像处理函数-图像拼接-hconcat、vconcat、Stitcher

在图像处理和计算机视觉领域,图像拼接是一个常见的操作。OpenCvSharp是一个用于.NET平台的OpenCV封装库,可以方便地进行图像处理。本文将详细介绍如何使用OpenCvSharp中的hconcat、vconcat函数以及Stitcher类进行图像拼接,并通过具体示例帮助读者理解和掌握这些知识点。 函…

Java生成NetCDF文件

因为需要再Cesium中实现风场粒子效果&#xff0c;网上找了许多项目&#xff0c;大多是通过加载NC文件来进行渲染的&#xff0c;因此了解NC文件又成了一件重要的事。特此记录用java成果生成可在前端渲染&#xff0c;QGIS中正常渲染的NetCDF文件的相关代码&#xff08;有没详细整…

16. 第十六章 类和函数

16. 类和函数 现在我们已经知道如何创建新的类型, 下一步是编写接收用户定义的对象作为参数或者将其当作结果用户定义的函数. 本章我会展示函数式编程风格, 以及两个新的程序开发计划.本章的代码示例可以从↓下载. https://github.com/AllenDowney/ThinkPython2/blob/master/c…

java程序在运行过程各个内部结构的作用

一&#xff1a;内部结构 一个进程对应一个jvm实例&#xff0c;一个运行时数据区&#xff0c;又包含多个线程&#xff0c;这些线程共享了方法区和堆&#xff0c;每个线程包含了程序计数器、本地方法栈和虚拟机栈接下来我们通过一个示意图介绍一下这个空间。 如图所示,当一个hell…

内窥镜系统设计简介

内窥镜系统设计简介 1. 源由2. 系统组成2.1 光学系统2.2 机械结构2.3 电子系统2.4 软件系统2.5 安全性和合规性2.6 研发与测试2.7 用户培训与支持 3. 研发过程3.1 光学系统Step 1&#xff1a;镜头设计Step 2&#xff1a;光源Step 3&#xff1a;成像传感器 3.2 机械结构Step 1&a…

11.泛型、trait和生命周期(上)

标题 一、泛型数据的引入二、改写为泛型函数三、结构体/枚举中的泛型定义四、方法定义中的泛型 一、泛型数据的引入 下面是两个函数&#xff0c;分别用来取得整型和符号型vector中的最大值 use std::fs::File;fn get_max_float_value_from_vector(src: &[f64]) -> f64…

代码随想录-Day31

455. 分发饼干 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i]&#xff0c;这是能让孩子们满足胃口的饼干的最小尺寸&#xff1b;并且每块饼干 j&#xff0c;都…

Python中的命名空间和作用域:解密变量的可见性和生命周期

在 Python 中&#xff0c;命名空间&#xff08;Namespace&#xff09;和作用域&#xff08;Scope&#xff09;是重要的概念&#xff0c;它们决定了变量和函数的可见性和生命周期。理解命名空间和作用域是编写高效、可维护代码的关键。 基本语法 命名空间 命名空间是一个存储…

新视野大学英语2 词组 6.16

decide between rival options 在互相竞争的选项中做出选择 chinese imperial general 中国帝国将军 on a raid into enemy territory 深入敌方领土突袭 on a raid into&#xff1a;“在进入……的突袭行动中”。 通常指军事行动中快速、秘密地侵入敌人控制的区域&#xff0c…

oracle打补丁

1.备份 su - grid -c "crsctl status res -t" cat /proc/meminfo | grep HugePagesls -lrt /dev/ls -lrt /dev/sd*ls -lrt /dev/asm*cat /etc/udev/rules.d/asm***df -hmountfree -g/etc/security/limits.conf/etc/hosts/etc/selinux/config /etc/pam.d/system-aut…

vs+qt5.0 使用poppler 操作库

Poppler 是一个用来生成 PDF 的C类库&#xff0c;从xpdf 继承而来。vs编译库如下&#xff1a; vs中只需要添加依赖库即可 头文件&#xff1a;

从MySQL到NoSQL:分析传统关系型数据库与NoSQL数据库的协同

引言 数据库是一个系统,用来管理和存储数据的地方。数据在数据库中以一种结构化的方式组织,这样能更容易地查询和处理数据。 关系型数据库是基于关系模型的数据库,它将数据存储在不同的表中,每个表都有各自的独一无二的主键。表与表之间通过共享的数据项相互关联。像MySQ…

windows11 生产力工具配置

一、系统安装 官方windows11.iso镜像文件安装操作系统时&#xff0c;会强制要求联网验证&#xff0c;否则无法继续安装操作系统&#xff0c;跳过联网登录账号的方式为&#xff1a;按下【shiftF10】快捷键&#xff0c;调出cmd命令窗口&#xff0c;输入命令 OOBE\BYPASSNRO 等…

【博客720】时序数据库基石:LSM Tree的辅助优化

时序数据库基石&#xff1a;LSM Tree的辅助优化 场景&#xff1a; LSM Tree其实本质是一种思想&#xff0c;而具体是否需要WAL&#xff0c;内存表用什么有序数据结构来组织&#xff0c;磁盘上的SSTable用什么结构来存放&#xff0c;是否需要布隆过滤器来加快不存在数据的判断等…

Python笔记 - TOML配置文件

TOML&#xff08;Tom’s Obvious, Minimal Language&#xff09;是一种配置文件格式&#xff0c;旨在比JSON、YAML等格式更易读、更人性化。它使用简洁的语法&#xff0c;能清晰地表达复杂的结构&#xff0c;同时保留良好的可读性。本文将介绍TOML的基本语法&#xff0c;提供代…

【UE5|水文章】在UMG上显示帧率

参考视频&#xff1a; https://www.youtube.com/watch?vH_NdvImlI68 蓝图&#xff1a;

数值分析笔记(二)函数插值

函数插值 已知函数 f ( x ) f(x) f(x)在区间[a,b]上n1个互异节点 { x i } i 0 n \{{x_i}\}_{i0}^{n} {xi​}i0n​处的函数值 { y i } i 0 n \{{y_i}\}_{i0}^{n} {yi​}i0n​&#xff0c;若函数集合 Φ \Phi Φ中函数 ϕ ( x ) \phi(x) ϕ(x)满足条件 ϕ ( x i ) y i ( i …