1、
所谓模板特例,是针对符合某种条件的模板参数值集合另外声明的模板实现变体。
template<typename T> class my_vector;
template<> class my_vector<bool>;
 
2、特例的多种写法
template<typename T, int i> struct S1;

template<typename T, int i, template<typename, int> class SP>
struct S;//这里先定义一个模板通例 template<int i, template<typename, int> class SP>
struct S<char, i, SP>;//省略了typename T template<typename T, int i, template<typename, int> class SP>
struct S<const T, i, SP>;//约束第一个模板参数的类型必须用const修饰 template<>
struct S<char, , S1>;//完全特例,其匹配式中没有用到任何模板参数,所有项目都是确定的类型或值或模板。故其模板参数列表为空。相应的,我们将模板参数列表不为空的特例成为部分特例 template<typename T>
struct S<S1<T, >, , S1>;//难点理解:同特例2一样,这里也是对T的一种约束,它要求S这个模板的第一个参数必须是S1<T, 10>模板的实例,而第三个参数要是S1型的某个模板,这里不要搞混(搞混的话就回头去看模板型模板参数)
3、特例的匹配规则
template<typename T0, typename T1, typename T2>
struct S
{
std::string id()
{
return "General";
}
}; template<typename T0, typename T1>
struct S<T0, T1, char>
{
std::string id()
{
return "Specialization #1";
}
}; template<typename T0>
struct S<T0, char, char>
{
std::string id()
{
return "Specialization #2";
}
}; template<typename T>
struct S<int, T, T>
{
std::string id()
{
return "Specialization #3";
}
}; void main()
{
using namespace std;
printf("%s\n", S<float, float, float>().id().c_str());//匹配通例
printf("%s\n", S<int, int, int>().id().c_str());//只与特例3匹配
printf("%s\n", S<int, int, char>().id().c_str());//与特例1匹配,因为特例3要求后两个类型是相同的
printf("%s\n", S<char, char, char>().id().c_str());//与特例1和特例2均匹配,但特例2比特例1更加特殊
printf("%s\n", S<int, char, char>().id().c_str());//在特例2和特例3之间无法判断谁更特殊 getchar();
}
4、函数模板的特例与重载
函数模板特例的写法与类模板特例的写法十分相似。
函数重载是要匹配函数的参数,而函数模板特例则是要匹配模板参数。粗看二者似乎并不想干,但C++中海油一个函数模板参数推导机制。当没有显式给定模板实参值时,编译器会尝试由函数调用的实参类型推导出模板参数值。有此推导机制,函数模板参数值在某些情况下也可与函数调用的实参相关。
特例与重载这两种既想干又不完全等价的机制并存,使得处理函数模板变得异常复杂且潜藏众多冲突。为化简问题,C++标准中只允许为函数模板声明完全特例,而禁止为其声明部分特例。大多数需要用到部分 特例的情况都可以利用函数模板重载来实现。
 
区分模板特例与重载:
准则一:两候选函数中如果有一方其形参列表个类型与调用实参列表各类型更匹配,则淘汰另一方。
准则二:两函数如果其形参列表类型同等匹配实参列表类型时,若一方为函数模板实例而另一方为非模板函数,则去非模板函数而淘汰函数模板实例。
准则三:两函数如果其形参列表类型同等匹配实参列表类型时,若两者均为函数模板实例,则去更为特殊的一方淘汰另一方。
template<typename T>
void func(T v)
{
printf("#1\n");
} template<>
void func(float v)
{
printf("#2\n");
} void func(float v)
{
printf("#3\n");
} void main()
{
func();//只有#1才有可能是int,其它全是float
func(.);//1.是double类型,只有#1才有可能是double,其它全是float
func(.f);//#1和#2和#3都与实参类型完全匹配。#2比#1更加特殊,淘汰掉#1。#3与#2之间比较选择非模板函数#3。
func<>(.f);//这里显式指定要使用的是一个模板实例 getchar();
}

编译期递归逻辑:
#2与#3接口完全一致。也就是说,你可以完全不用#3这个完全模板特例,而只使用#2这样的重载替代模板完全特例就好了。因为,前面的#2中,func(float v)可以通过实参的float推导出模板中的T是float,所以使用#3func重载的效果和使用完全模板特例是一样的,没必要再设完全模板特例了,(也就是说通过实际使用参数float v已经推断出了T,但是如果实参和模板参数没有联系呢?)那么模板完全特例存在的意义为何?
比如说,有模板参数不与函数参数类型相关,此时的模板特例不能用函数重载替代。例如,模板有个非模板型模板参数,则显然不能通过调用实参类型推导,而需要在模板实参列表中给定明确的值。
举例,
template<int i>
void print(float f)
{
//这里边使用i
}
 
这个模板的模板参数与函数实参没有任何关系,且唯一的模板参数是非模板型模板参数。那么根据 i 的取值不同,其模板实例的意义不同。你给print<1>~print(100)都设为特例则有可能有100个不同的意义,所以如果有一个重载函数void print(float f),你知道这个print()要在何时执行吗哪一个吗?
print<1>(float);//完全特例
print<100>(float);//完全特例
假如有一个,print(float)你不知道这里的值到底是1-100实例中的哪一个,这里就不能用函数重载替代完全模板特例。
现在,print(1.)就完全不能代替模板了。
 
实例如下:
template<int i>
void print()
{
print<i - >();
printf("%d\n", i);
} template<>
void print<>()
{
printf("1!\n");
} void main()
{
print<>();
getchar();
}
 
 
 
 

《深入实践C++模板编程》之四——特例的更多相关文章

  1. 《深入实践C++模板编程》之五——容器与迭代器

    1.容器的定义 容器:专门用于某种形式组织及存储数据的类称为“容器”.   2.容器与迭代器 迭代器:封装了对容器虚拟数据序列的操作并按约定提供统一界面以遍历容器内容的代理类即为迭代器.   举例理解 ...

  2. 《深入实践C++模板编程》之六——标准库中的容器

    1.容器的基本要求 a.并非所有的数据都可以放进容器当中.各种容器模板对所存数据类型都有一个基本要求——可复制构造.将数据放进容器的过程就是通过数据的复制构造函数在容器内创建数据的一个副本的过程. b ...

  3. 《深入实践C++模板编程》之三——模板参数类型详解

    非类型模板参数 和 模板型模板参数 整数以及枚举类型:指向对象或者函数的指针:对对象或函数的引用:指向对象成员的指针.统称为非类型模板参数. 模板型模板参数,是指模板参数还可以是一个模板.   1.整 ...

  4. 《深入实践C++模板编程》之二——模板类

    1.类的模板的使用 类,由于没有参数,所以没有模板实参推导机制. #include <stdexcept> template<typename T> class my_stac ...

  5. 《深入实践C++模板编程》之一——Hello模板

    1.通过一个简单的例子来理解模板的用途: 模板为不同类型的数据生成操作相同或相似的函数. 弱语言如Python,可以使用一种函数来应对各种类型,但是C++就不得不为不同的类型编写相似的函数.模板的作用 ...

  6. C++之模板编程

    当我们越来越多的使用C++的特性, 将越来越多的问题和事物抽象成对象时, 我们不难发现:很多对象都具有共性. 比如 数值可以增加.减少:字符串也可以增加减少. 它们的动作是相似的, 只是对象的类型不同 ...

  7. 深入C++04:模板编程

    模板编程 函数模板 模板意义:对类型也进行参数化: 函数模板:是不编译的,因为类型不知道 模板的实例化:函数调用点进行实例化,生成模板函数 模板函数:这才是要被编译器所编译的 函数模板.模板的特例化. ...

  8. c++模板编程-typename与class关键字的区别

    最近一直在研究c++模板编程,虽然有些困难,但希望能够坚持下去.今天,在书上看见一个讨论模板编程typename与class两个关键字的区别,觉得挺有意义的,就把它们给总结一下. 先看一个例子: te ...

  9. [推荐]ORACLE PL/SQL编程之四:把游标说透(不怕做不到,只怕想不到)

    原文:[推荐]ORACLE PL/SQL编程之四:把游标说透(不怕做不到,只怕想不到) [推荐]ORACLE PL/SQL编程之四: 把游标说透(不怕做不到,只怕想不到) 继上两篇:ORACLE PL ...

随机推荐

  1. Linux kernel device mapper

    Device Mapper 是 Linux2.6 内核中支持逻辑卷管理的通用设备映射机制,它为实现用于存储资源管理的块设备驱动提供了一个高度模块化的内核架构,如图 1. 图1 Device Mappe ...

  2. Netfilter 之 iptable_nat

    初始化 iptable_nat_table_init函数通过调用ipt_register_table完成NAT表注册和钩子函数注册的功能:该流程与iptable_filter的函数调用的函数一致,此处 ...

  3. TensorFlow错误ValueError: No gradients provided for any variable

    使用TensorFlow训练神经网络的时候,出现以下报错信息: Traceback (most recent call last):   File "gan.py", line 1 ...

  4. 更新ubuntu的对应源配置文件

    UBUNTU中安装依赖包,出现如下错误:E: Failed to fetch http://security.ubuntu.com/ubuntu/pool/universe/o/openjdk-8/o ...

  5. 简易总结react-hook三大基础

    react-hook的最重要的三大基础 import { useEffect, useState, useContext } from 'react' 1.每一个简单的一句话总结 useEffect ...

  6. vue-判断设备是手机端还是pc端

    经常在项目中会有支持 pc 与手机端需求.并且pc与手机端是两个不一样的页面.这时就要求判断设置,根据不同的设置跳转不同的路由. [代码演示] 在 router/index.js 中有两个页面. ex ...

  7. leetcode25 K 个一组翻转链表

    这道题关于链表的操作,中间指针操作略复杂. /** * Definition for singly-linked list. * struct ListNode { * int val; * List ...

  8. thinkphp5的控制器调用自身模块和调用其他模块的方法

    以user为例,调用user.php的get_number()方法 一.不管是调用自身模块还是其他模块app\model\User.php写法不变 <?php namespace app\ind ...

  9. Ajax案例-基于XML,以POST方式,完成省份-城市二级下拉联动

    <%@ page language="java" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC & ...

  10. UITableView+FDTemplateLayoutCell计算行高显示<二>

    之前记录过一篇UITableView+FDTemplateLayoutCell计算行高不成功的博客... 传送门:http://www.cnblogs.com/pengsi/p/6571311.htm ...