为什么要引入

单一模板很难兼顾各种情况。为此,C++提供了一种特例机制,对于某个已有模板,可以为某个或者某组模板参数类型另外指定一种变体,以应对原模板无法处理的情况,或者提供更高效的实现方案,这就是模板特例

实例:vector<bool>

vector<bool>vector<T>的一个特例,专为存储bool型值设计。现在我们自己写一个vector<bool>来为存储bool值提供更高效的方案吧。功能:

  • 构造函数中需要声明my_vector初始大小
  • 可以通过成员函数push_back()向my_vector中增加元素
  • 重载下标运算符[]共读写my_vector中元素
template<typename T>
class my_vector{T* array;unsigned size;unsigned block_size;
public:my_vector(unsigned bsz) :array((T*)malloc(sizeof(T) * bsz)), size(0), block_size(bsz) {}~my_vector() {if(array)free(array);}void push_back(T const &elem) noexcept(false){if(size == block_size){block_size *= 2;T* new_array = (T*) realloc(array, block_size * sizeof(T));if(new_array){array = new_array;}else{free(array);array = nullptr;throw std::runtime_error("Out of memory");}}array[size++] = elem;}T& operator[](unsigned i){return array[i];}const T& operator[](unsigned i) const {return array[i];}unsigned get_mem_size() const {return block_size * sizeof(T);}
};

sizeof(bool)的返回值为1,即1字节(byte)。但是bool型变量只有两个值,只需要1bit就可以表示,一个字节可以用来存储8个bool值。位置,我们设置了一个bool型的模板特例。当使用my_vector<bool>编译器会自动选用预设的特别方案。

  • 特例是相对于通例而言的。也就是说,必须先有my_vector<T>才可以有my_vector<bool>
  • 所谓模板特例,是针对符合某种条件的模板参数值集合另外声明的模板实现变体。所以,每个特例都需要有模板参数匹配式以表明其适用范围。在声明特例时,匹配式写在模板名后面并由<>括起。而原本的模板参数列表改为匹配式所用模板参数列表。说人话就是:
template<typename T> class my_vector; // 通例
template<> class my_vector<bool>; // 特例

下面我们来看具体实现:

template<>
class my_vector<bool>{int *array;unsigned size;unsigned block_size;const static unsigned seg_size;
public:my_vector(unsigned  bsz = 1) :array((int *) malloc(sizeof(int) * bsz)),size(0),block_size(bsz){}~my_vector() {if(array){free(array);}}void push_back(bool elem) noexcept(false){if(size == block_size * seg_size){block_size *= 2;int* new_array = (int*) realloc(array, block_size * sizeof(int));if(new_array){array = new_array;}else{free(array);array = nullptr;throw std::runtime_error("Out of memory");}}set(size++, elem);}void set(unsigned i, bool elem){if(elem){array[i/seg_size] |= (0x1 << (i % seg_size));}else{array[i/seg_size] &= ~(0x1 << (i % seg_size));}}bool operator[] (unsigned i)const{return (array[i/seg_size] & (0x1 << (i % seg_size))) != 0;}unsigned get_mem_size() const {return block_size * sizeof(int);}
};const unsigned my_vector<bool>::seg_size = sizeof(int ) * 8;

使用:

int main(){my_vector<char> vi(2);my_vector<bool> vb(2);for(unsigned  i = 0; i < 20; i++){vi.push_back('a' + i);vb.push_back( (i % 2) == 0);}printf("%d, %d\n", vi.get_mem_size(), vb.get_mem_size());for(unsigned  i = 0; i < 20; i++){std::cout << ' ' << vi[i];}std::cout << "\n";for(unsigned  i = 0; i < 20; i++){std::cout << ' ' << vb[i];}std::cout << "\n";
}

C++对于特例和通例没有任何约束,理论上可以把特例设计成一个与通例毫无共同点的例,不过最好不要这样做。

类模板

特例的多种写法

// 用于模板型模板参数的模板
template<typename T, int i> class S1;// 模板通例,有三个模板参数:类型参数,非类型参数、模板型参数
template<typename T, int i, template<typename, int>class SP>
struct S;// 特例1:可以匹配S<char, 任意整数,S1>
template<int i, template<typename, int>class SP>
struct S<char, i, SP>;// 特例2: 可以匹配S<任意有const修饰的类型, 任意整数, S1>
template<typename T, int i, template<typename, int>class SP>
struct S<const T, i, SP>;// 特例3: 完全特例,只能匹配S<char, 10, S1>
template<>
struct S<char, 10, S1>//特例4:以模板实例作为类型参数值,匹配S<S1<任意类型, 10>, 10, S1>
template<typename T>
struct S<S1<T, 10>, 10, S1>//特例5:错误!匹配项目数和通例个数不一致
template<typename T, int i, template<typename, int>class SP, template TT>
struct S<const T, i, SP, TT>;// 特例6:错误!匹配式项目类型和通例参数类型不一致
template<typename T>
struct S<char, 10, T>//特例7: 错误! 模板型参数SP和通例中的SP类型不一致
template<typename T, int i, template<typename>class SP>
struct S<const T, i, SP>;

特例匹配规则

同一模板中可以有多个实例。

  • 当某套模板参数能与多个特例匹配时,编译器会从中优先选择最“特殊”的一个。
  • 如果有任意两个特例之间无法确定谁更特殊时,则代码有歧义,编译报错
#include <iostream>template<typename T0, typename T1, typename T2>
struct S{std::string id(){return "general";}
};// 特例1:约束第3参数必须为char
template<typename T0, typename T1>
struct S<T0, T1, char>{std::string id(){return "specializetion #1";}
};// 特例2:约束第2、3参数必须为char
template<typename T0>
struct S<T0, char, char>{std::string id(){return "specializetion #2";}
};// 特例3:约束第1参数必须为int, 第2、3参数必须相同
template<typename T0>
struct S<int, T0, T0>{std::string id(){return "specializetion #3";}
};int main(){printf("%s\n", S<float, float, float>().id().c_str());  //实例1printf("%s\n", S<int, int, int>().id().c_str());  //实例2printf("%s\n", S<int, int, char>().id().c_str());  //实例3printf("%s\n", S<char, char, char>().id().c_str());  //实例4// printf("%s\n", S<int, char, char>().id().c_str());  //实例5, 有歧义
}

模板函数中的特例与重载

C++标准中只允许为函数模板声明完成特例,禁止为其声明部分特例

#include <iostream>template<typename T>
void print(T v){std::cout << v << std::endl;
}//模板特例
template<>
void print<char>(char v){std::cout << "\'"<< v << "\'"<<  std::endl;
}//模板特例,模板参数依赖推导
template<>
void print(const char *v){std::cout << "\""<< v << "\""<<  std::endl;
}// 函数重载
inline
void print(std::string const &v){std::cout << "\""<< v << "\""<<  std::endl;
}inline
void print(bool v){std::cout << std::boolalpha << v << "\n";
}// 函数模板重载
template<typename T>
void print(T *v){std::cout << "*";print(*v);
}

分辨重载

编译器分辨重载函数的基本过程:

  • 首先,对于某个函数调用,编译器已知所调用函数名、参数个数和类型等。
  • 当发现有同名重载函数模板时,会根据参数类型一次尝试推导出模板参数类型
  • 如果推导成功,则生成对应函数模板实例作为候选
  • 如果发现有同名函数,也将该函数列入候选
  • 但是如果调用明确要求一个模板实例,即函数名后紧跟一对<>,则不将普通重载函数列入候选
  • 至此,候选集合中无论函数模板实例还是普通函数,都可以视为有效的函数定义(而非模板),从而可以按照C++中对普通重载函数调用分辨重载的规则确定最佳选择。

我们只需要知道这三个分辨重载的准则即可:

  • 两候选函数中如果有一方其形参列表各类型与调用实参各类型更匹配,则淘汰另一方。

    • 参数匹配类型的一般原则是:匹配度从高到低

      • 等价类型: 比如char[]和char*;由实参类型char *转换到形参类型const char*
      • 标准类型转换:比如实参类型int转换成long
      • 自定义类型转换:比如将实参类型int转换成用户定义类型A,而且A有构造函数A(int)
  • 两函数如果其形参列表同等匹配实参列表类型时,如果一方为函数模板实例二另一方为非模板函数,则取非模板函数而淘汰函数模板实例
  • 两函数如果其形参列表同等匹配实参列表类型时,如果两者均为函数模板实例,而取更为特殊的一方而淘汰另一方。

看个例子:

#include <iostream>template<typename T>
void func(T v){std::cout << "#1:" << v << "\n";
}template<>
void func(float v){std::cout << "#2:" << v << "\n";
}void func(float v){std::cout << "#3:" << v << "\n";
}int main(){func(1);func(1.);func(1.f);func<>(1.f);
}


分析:

  • func(1):因为函数模板#1可以根据实参类型推导出模板参数T=int,所生成的函数实例中形参与实参类型完全一致,所以#1
  • func(1.):推导出模板参数为T=double,因此同上
  • func(1.f):1.f指定为double,因此选择非模板函数#3
  • func<>(1.f)显式的指出了一个模板实例调用,因此不考虑#3,又因为#2比#1更特殊,所以选择#2

注意:

  • 模板实例(#2)和重载普通函数(#3)都接受一个浮点型参数,二者的参数定义完全一致。但这并不违法C++的唯一性原则。模板不是普通函数,虽然给模板特例没有模板参数,但仍然是一个模板。编译器只有在明确需要生成模板实例时才会依据模板内容生成具体函数,所以该重载模板与重载函数不冲突

编译期的条件判断逻辑

招聘题目:不使用模板以及条件判断语句,打印1-100个数组

#include <iostream>template<int N>
void print(){std::cout << N << "\t";print<N-1>();
}// 特例:终止递归
template<>
void print<1>(){std::cout << 1 << "\t";
}int main(){print<100>();
}

C/C++编程:模板特例相关推荐

  1. 模板编程:模板特例化以及特例化inline的做用

    重点: 1.模板特例化就是给模板一个特殊的定义.比如正方形是长方形中一种特殊的情况,而这个正方形就是特例化. 2.我们不需要也不能够对特例化的版本进行声明:只能定义它们.由于这是一个定义于头 文件中的 ...

  2. 创建模板_UG中如何创建属于自己的编程模板界面?

    点击关注 不迷路 ◆UG12如何实现多窗口显示部件 ◆[回转]命令 ◆实体建模工具拉伸 ◆UG12如何提醒自动保存时间 ◆UG12如何对实体产品剖视 先给大家出一道感性的推理题: 从前,有一个被巫师施 ...

  3. 单机编程c语言,完美的8051单机C语言编程模板.doc

    完美的8051单机C语言编程模板 <8051单片机C语言编程模板> [程序开始处的程序说明] /********************************************** ...

  4. C++ Primer 5th笔记(chap 16 模板和泛型编程)模板特例化

    1. 场景 //第一个版本:可以比较任意两个类型 template <typename T> int compare (const T &, const T & );//第 ...

  5. STM32F103五分钟入门系列(一)跑马灯(库函数+寄存器)+加编程模板+GPIO总结

    摘自:STM32F103五分钟入门系列(一)跑马灯(库函数+寄存器)+加编程模板+GPIO总结 作者:自信且爱笑' 发布时间: 2021-04-28 21:17:40 网址:https://blog. ...

  6. 大学c语言编程模板,c语言编程模板

    <c语言编程模板>由会员分享,可在线阅读,更多相关<c语言编程模板(8页珍藏版)>请在人人文库网上搜索. 1.单片机C语言编程模板(基础模板) 程序开始处的程序说明 /* * ...

  7. 引用另一模板的宏_生信人值得拥有的编程模板Shell

    前言 "工欲善其事必先利其器",生信工程师每天写代码.搭流程,而且要使用至少三门编程语言,没有个好集成开发环境(IDE,Integrated Development Environ ...

  8. 杜洋单片机C语言编程组成,8051单片机C语言编程模板

    c语言 本文由fan159147贡献 doc文档可能在WAP端浏览体验不佳.建议您优先选择TXT,或下载源文件到本机查看. <8051 单片机 C 语言编程模板> 杜洋 2009.7 [程 ...

  9. 单片机c语言中void key(void),STC单片机C语言通用万能编程模板

    <STC单片机C语言通用万能编程模板>由会员分享,可在线阅读,更多相关<STC单片机C语言通用万能编程模板(23页珍藏版)>请在人人文库网上搜索. 1.8051 单片机 C 语 ...

最新文章

  1. bwapp之xss(blog)
  2. linux下磁盘是硬盘吗,肿么确定linux系统上的硬盘哪个是主盘
  3. 魔术引号 php,php怎么关闭魔术引号
  4. Jackson中的自定义反序列化器和验证
  5. int 转interger java_Java中Integer和int之间的转换
  6. 生日快乐!中国航天员“天团”
  7. java 解析/操作 xml 几种常用方式 xml的增加/删除/修改
  8. 解析恶意软件***技术特点
  9. 出现net.sf.json.JSONException: There is a cycle in the hierarchy异常的解决办法
  10. CSDN网站系统升级公告
  11. MYSQL命令行闪退问题解决
  12. php如何把pdf转图片,PHP中使用imagick实现把PDF转成图片
  13. paip 输入法编程----二级汉字2350个常用汉字2350个
  14. UBUNTU开启CRONTAB日志记录及解决NO MTA INSTALLED, DISCARDING OUTPUT问题
  15. 【LGCN】如何理解Large-Scale Learnable Graph Convolutional Networks?
  16. ThreeJS FBXLoader 加载3D文件,材质消失,已解决
  17. 电商系统-优惠券部分设计
  18. Snapchat争先恐后地修复失败的重新设计,将故事转移到发现之中
  19. QFP PQFP LQFP TQFP封装区别!
  20. vue实现模拟象棋走子

热门文章

  1. allow_pickle什么意思_in pickle是什么意思
  2. java模拟国际象棋游戏_Javafx实现国际象棋游戏
  3. MySQL核心技术(持续更新)
  4. 第1章第17节:如何使用备注功能对内容进行注释补充 [PowerPoint精美幻灯片实战教程]
  5. Tita OKRs-E 使OKR成为您企业DNA一部分
  6. python 豆瓣源_使用douban源下载python包
  7. 门窗软件测试自学,AutoCAD 2014室内装潢设计完全自学手册[9787111482352]
  8. PBR中引入IBL——漫反射篇
  9. LTE学习笔记:OFDM
  10. icc 颜色 c语言,浅析颜色在icc中四种不同的转换方式