Compare commits

...

2 Commits

Author SHA1 Message Date
Ye Wu cd4232437e Reclarified 术语 模板类 和 类模板 的正确使用 2022-10-11 19:19:00 -07:00
Ye Wu c0e945d764 更新了TOC,修正了部分行文。 2022-10-11 18:55:09 -07:00
1 changed files with 156 additions and 164 deletions

320
ReadMe.md
View File

@ -1,71 +1,53 @@
# C++ Template 进阶指南
C++ Template 进阶指南 <!-- omit in toc -->
=================
章节目录由VSCode插件[Markdown All in One](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one)生成。
- [C++ Template 进阶指南](#c-template-%e8%bf%9b%e9%98%b6%e6%8c%87%e5%8d%97)
- [0. 前言](#0-%e5%89%8d%e8%a8%80)
- [0.1 C++另类简介:比你用的复杂,但比你想的简单](#01-c%e5%8f%a6%e7%b1%bb%e7%ae%80%e4%bb%8b%e6%af%94%e4%bd%a0%e7%94%a8%e7%9a%84%e5%a4%8d%e6%9d%82%e4%bd%86%e6%af%94%e4%bd%a0%e6%83%b3%e7%9a%84%e7%ae%80%e5%8d%95)
- [0.2 适宜读者群](#02-%e9%80%82%e5%ae%9c%e8%af%bb%e8%80%85%e7%be%a4)
- [0.3 版权](#03-%e7%89%88%e6%9d%83)
- [0.4 推荐编译环境](#04-%e6%8e%a8%e8%8d%90%e7%bc%96%e8%af%91%e7%8e%af%e5%a2%83)
- [0.5 体例](#05-%e4%bd%93%e4%be%8b)
- [0.5.1 示例代码](#051-%e7%a4%ba%e4%be%8b%e4%bb%a3%e7%a0%81)
- [0.5.2 引用](#052-%e5%bc%95%e7%94%a8)
- [0.6 意见、建议、喷、补遗、写作计划](#06-%e6%84%8f%e8%a7%81%e5%bb%ba%e8%ae%ae%e5%96%b7%e8%a1%a5%e9%81%97%e5%86%99%e4%bd%9c%e8%ae%a1%e5%88%92)
- [1. Template的基本语法](#1-template%e7%9a%84%e5%9f%ba%e6%9c%ac%e8%af%ad%e6%b3%95)
- [1.1 Template Class基本语法](#11-template-class%e5%9f%ba%e6%9c%ac%e8%af%ad%e6%b3%95)
- [1.1.1 Template Class的与成员变量定义](#111-template-class%e7%9a%84%e4%b8%8e%e6%88%90%e5%91%98%e5%8f%98%e9%87%8f%e5%ae%9a%e4%b9%89)
- [1.1.2 模板的使用](#112-%e6%a8%a1%e6%9d%bf%e7%9a%84%e4%bd%bf%e7%94%a8)
- [1.1.3 模板类的成员函数定义](#113-%e6%a8%a1%e6%9d%bf%e7%b1%bb%e7%9a%84%e6%88%90%e5%91%98%e5%87%bd%e6%95%b0%e5%ae%9a%e4%b9%89)
- [1.2 Template Function的基本语法](#12-template-function%e7%9a%84%e5%9f%ba%e6%9c%ac%e8%af%ad%e6%b3%95)
- [1.2.1 Template Function的声明和定义](#121-template-function%e7%9a%84%e5%a3%b0%e6%98%8e%e5%92%8c%e5%ae%9a%e4%b9%89)
- [1.2.2 模板函数的使用](#122-%e6%a8%a1%e6%9d%bf%e5%87%bd%e6%95%b0%e7%9a%84%e4%bd%bf%e7%94%a8)
- [1.3 整型也可是Template参数](#13-%e6%95%b4%e5%9e%8b%e4%b9%9f%e5%8f%af%e6%98%aftemplate%e5%8f%82%e6%95%b0)
- [1.4 模板形式与功能是统一的](#14-%e6%a8%a1%e6%9d%bf%e5%bd%a2%e5%bc%8f%e4%b8%8e%e5%8a%9f%e8%83%bd%e6%98%af%e7%bb%9f%e4%b8%80%e7%9a%84)
- [2. 模板元编程基础](#2-%e6%a8%a1%e6%9d%bf%e5%85%83%e7%bc%96%e7%a8%8b%e5%9f%ba%e7%a1%80)
- [2.1 编程,元编程,模板元编程](#21-%e7%bc%96%e7%a8%8b%e5%85%83%e7%bc%96%e7%a8%8b%e6%a8%a1%e6%9d%bf%e5%85%83%e7%bc%96%e7%a8%8b)
- [2.2 模板世界的If-Then-Else类模板的特化与偏特化](#22-%e6%a8%a1%e6%9d%bf%e4%b8%96%e7%95%8c%e7%9a%84if-then-else%e7%b1%bb%e6%a8%a1%e6%9d%bf%e7%9a%84%e7%89%b9%e5%8c%96%e4%b8%8e%e5%81%8f%e7%89%b9%e5%8c%96)
- [2.2.1 根据类型执行代码](#221-%e6%a0%b9%e6%8d%ae%e7%b1%bb%e5%9e%8b%e6%89%a7%e8%a1%8c%e4%bb%a3%e7%a0%81)
- [2.2.2 特化](#222-%e7%89%b9%e5%8c%96)
- [2.2.3 特化:一些其它问题](#223-%e7%89%b9%e5%8c%96%e4%b8%80%e4%ba%9b%e5%85%b6%e5%ae%83%e9%97%ae%e9%a2%98)
- [2.3 即用即推导](#23-%e5%8d%b3%e7%94%a8%e5%8d%b3%e6%8e%a8%e5%af%bc)
- [2.3.1 视若无睹的语法错误](#231-%e8%a7%86%e8%8b%a5%e6%97%a0%e7%9d%b9%e7%9a%84%e8%af%ad%e6%b3%95%e9%94%99%e8%af%af)
- [2.3.2 名称查找I am who I am](#232-%e5%90%8d%e7%a7%b0%e6%9f%a5%e6%89%bei-am-who-i-am)
- [2.3.3 “多余的” typename 关键字](#233-%e5%a4%9a%e4%bd%99%e7%9a%84-typename-%e5%85%b3%e9%94%ae%e5%ad%97)
- [2.4 本章小结](#24-%e6%9c%ac%e7%ab%a0%e5%b0%8f%e7%bb%93)
- [3 深入理解特化与偏特化](#3-%e6%b7%b1%e5%85%a5%e7%90%86%e8%a7%a3%e7%89%b9%e5%8c%96%e4%b8%8e%e5%81%8f%e7%89%b9%e5%8c%96)
- [3.1 正确的理解偏特化](#31-%e6%ad%a3%e7%a1%ae%e7%9a%84%e7%90%86%e8%a7%a3%e5%81%8f%e7%89%b9%e5%8c%96)
- [3.1.1 偏特化与函数重载的比较](#311-%e5%81%8f%e7%89%b9%e5%8c%96%e4%b8%8e%e5%87%bd%e6%95%b0%e9%87%8d%e8%bd%bd%e7%9a%84%e6%af%94%e8%be%83)
- [3.1.2 不定长的模板参数](#312-%e4%b8%8d%e5%ae%9a%e9%95%bf%e7%9a%84%e6%a8%a1%e6%9d%bf%e5%8f%82%e6%95%b0)
- [3.1.3 模板的默认实参](#313-%e6%a8%a1%e6%9d%bf%e7%9a%84%e9%bb%98%e8%ae%a4%e5%ae%9e%e5%8f%82)
- [3.2 后悔药SFINAE](#32-%e5%90%8e%e6%82%94%e8%8d%afsfinae)
- [!!! 以下章节未完成 !!!](#%e4%bb%a5%e4%b8%8b%e7%ab%a0%e8%8a%82%e6%9c%aa%e5%ae%8c%e6%88%90)
- [4 元编程下的数据结构与算法](#4-%e5%85%83%e7%bc%96%e7%a8%8b%e4%b8%8b%e7%9a%84%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84%e4%b8%8e%e7%ae%97%e6%b3%95)
- [4.1 表达式与数值计算](#41-%e8%a1%a8%e8%be%be%e5%bc%8f%e4%b8%8e%e6%95%b0%e5%80%bc%e8%ae%a1%e7%ae%97)
- [4.1 获得类型的属性——类型萃取Type Traits](#41-%e8%8e%b7%e5%be%97%e7%b1%bb%e5%9e%8b%e7%9a%84%e5%b1%9e%e6%80%a7%e7%b1%bb%e5%9e%8b%e8%90%83%e5%8f%96type-traits)
- [4.2 列表与数组](#42-%e5%88%97%e8%a1%a8%e4%b8%8e%e6%95%b0%e7%bb%84)
- [4.3 字典结构](#43-%e5%ad%97%e5%85%b8%e7%bb%93%e6%9e%84)
- [4.4 “快速”排序](#44-%e5%bf%ab%e9%80%9f%e6%8e%92%e5%ba%8f)
- [4.5 其它常用的“轮子”](#45-%e5%85%b6%e5%ae%83%e5%b8%b8%e7%94%a8%e7%9a%84%e8%bd%ae%e5%ad%90)
- [5 模板的进阶技巧](#5-%e6%a8%a1%e6%9d%bf%e7%9a%84%e8%bf%9b%e9%98%b6%e6%8a%80%e5%b7%a7)
- [5.1 嵌入类](#51-%e5%b5%8c%e5%85%a5%e7%b1%bb)
- [5.2 Template-Template Class](#52-template-template-class)
- [5.3 高阶函数](#53-%e9%ab%98%e9%98%b6%e5%87%bd%e6%95%b0)
- [5.4 闭包:模板的“基于对象”](#54-%e9%97%ad%e5%8c%85%e6%a8%a1%e6%9d%bf%e7%9a%84%e5%9f%ba%e4%ba%8e%e5%af%b9%e8%b1%a1)
- [5.5 占位符(placeholder)在C++中实现方言的基石](#55-%e5%8d%a0%e4%bd%8d%e7%ac%a6placeholder%e5%9c%a8c%e4%b8%ad%e5%ae%9e%e7%8e%b0%e6%96%b9%e8%a8%80%e7%9a%84%e5%9f%ba%e7%9f%b3)
- [5.6 编译期“多态”](#56-%e7%bc%96%e8%af%91%e6%9c%9f%e5%a4%9a%e6%80%81)
- [6 模板的威力从foreach, transform到Linq](#6-%e6%a8%a1%e6%9d%bf%e7%9a%84%e5%a8%81%e5%8a%9b%e4%bb%8eforeach-transform%e5%88%b0linq)
- [6.1 Foreach与Transform](#61-foreach%e4%b8%8etransform)
- [6.2 Boost中的模板](#62-boost%e4%b8%ad%e7%9a%84%e6%a8%a1%e6%9d%bf)
- [6.3 Reactor、Linq与C++中的实践](#63-reactorlinq%e4%b8%8ec%e4%b8%ad%e7%9a%84%e5%ae%9e%e8%b7%b5)
- [6.4 更高更快更强从Linq到FP](#64-%e6%9b%b4%e9%ab%98%e6%9b%b4%e5%bf%ab%e6%9b%b4%e5%bc%ba%e4%bb%8elinq%e5%88%b0fp)
- [7 结语:讨论有益,争端无用](#7-%e7%bb%93%e8%af%ad%e8%ae%a8%e8%ae%ba%e6%9c%89%e7%9b%8a%e4%ba%89%e7%ab%af%e6%97%a0%e7%94%a8)
- [7.1 更好的编译器,更友善的出错信息](#71-%e6%9b%b4%e5%a5%bd%e7%9a%84%e7%bc%96%e8%af%91%e5%99%a8%e6%9b%b4%e5%8f%8b%e5%96%84%e7%9a%84%e5%87%ba%e9%94%99%e4%bf%a1%e6%81%af)
- [7.2 模板的症结:易于实现,难于完美](#72-%e6%a8%a1%e6%9d%bf%e7%9a%84%e7%97%87%e7%bb%93%e6%98%93%e4%ba%8e%e5%ae%9e%e7%8e%b0%e9%9a%be%e4%ba%8e%e5%ae%8c%e7%be%8e)
- [7.3 一些期望](#73-%e4%b8%80%e4%ba%9b%e6%9c%9f%e6%9c%9b)
- [1. 前言](#1-前言)
- [1.1. C++另类简介:比你用的复杂,但比你想的简单](#11-c另类简介比你用的复杂但比你想的简单)
- [1.2. 适宜读者群](#12-适宜读者群)
- [1.3. 版权](#13-版权)
- [1.4. 推荐编译环境](#14-推荐编译环境)
- [1.5. 体例](#15-体例)
- [1.5.1. 示例代码](#151-示例代码)
- [1.5.2. 引用](#152-引用)
- [1.6. 意见、建议、喷、补遗、写作计划](#16-意见建议喷补遗写作计划)
- [2. Template的基本语法](#2-template的基本语法)
- [2.1. 什么是模板(Template)](#21-什么是模板template)
- [2.2. 类模板 (Class Template) 的基本语法](#22-类模板-class-template-的基本语法)
- [2.2.1. “模板类”还是“类模板”](#221-模板类还是类模板)
- [2.2.2. Class Template的与成员变量定义](#222-class-template的与成员变量定义)
- [2.2.3. 模板的使用](#223-模板的使用)
- [2.2.4. 类模板的成员函数定义](#224-类模板的成员函数定义)
- [2.3. 函数模板 (Function Tempalte) 入门](#23-函数模板-function-tempalte-入门)
- [2.3.1. 函数模板的声明和定义](#231-函数模板的声明和定义)
- [2.3.2. 函数模板的使用](#232-函数模板的使用)
- [2.4. 整型也可是Template参数](#24-整型也可是template参数)
- [2.5. 模板形式与功能是统一的](#25-模板形式与功能是统一的)
- [3. 模板元编程基础](#3-模板元编程基础)
- [3.1. 编程,元编程,模板元编程](#31-编程元编程模板元编程)
- [3.2. 模板世界的If-Then-Else类模板的特化与偏特化](#32-模板世界的if-then-else类模板的特化与偏特化)
- [3.2.1. 根据类型执行代码](#321-根据类型执行代码)
- [3.2.2. 特化](#322-特化)
- [3.2.3. 特化:一些其它问题](#323-特化一些其它问题)
- [3.3. 即用即推导](#33-即用即推导)
- [3.3.1. 视若无睹的语法错误](#331-视若无睹的语法错误)
- [3.3.2. 名称查找I am who I am](#332-名称查找i-am-who-i-am)
- [3.3.3. “多余的” typename 关键字](#333-多余的--typename-关键字)
- [3.4. 本章小结](#34-本章小结)
- [4. 深入理解特化与偏特化](#4-深入理解特化与偏特化)
- [4.1. 正确的理解偏特化](#41-正确的理解偏特化)
- [4.1.1. 偏特化与函数重载的比较](#411-偏特化与函数重载的比较)
- [4.1.2. 不定长的模板参数](#412-不定长的模板参数)
- [4.1.3. 模板的默认实参](#413-模板的默认实参)
- [4.2. 后悔药SFINAE](#42-后悔药sfinae)
- [4.3. Concept “概念”:对模板参数约束的直接描述](#43-concept-概念对模板参数约束的直接描述)
- [4.3.1. “概念” 解决了什么问题](#431-概念-解决了什么问题)
- [4.3.2. "概念"入门](#432-概念入门)
- [5. 未完成章节](#5-未完成章节)
## 0. 前言
# 1. 前言
### 0.1 C++另类简介:比你用的复杂,但比你想的简单
## 1.1. C++另类简介:比你用的复杂,但比你想的简单
C++似乎从它为世人所知的那天开始便成为天然的话题性编程语言。在它在周围有着形形色色的赞美与贬低之词。当我在微博上透露欲写此文的意愿时,也收到了很多褒贬不一的评论。作为一门语言,能拥有这么多使用并恨着它、使用并畏惧它的用户,也算是语言丛林里的奇观了。
@ -83,7 +65,7 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言,
本文的写作初衷,就是通过“编程语言”的视角,介绍一个简单、清晰的“模板语言”。我会尽可能地将模板的诸多要素连串起来,用一些简单的例子帮助读者学习这门“语言”,让读者在编写、阅读模板代码的时候,能像 `if(exp) { dosomething(); }`一样的信手拈来,让“模板元编程”技术成为读者牢固掌握、可举一反三的有用技能。
### 0.2 适宜读者群
## 1.2. 适宜读者群
因为本文并不是用于C++入门,例子中也多少会牵涉一些其它知识,因此如果读者能够具备以下条件,会读起来更加轻松:
@ -91,17 +73,17 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言,
* 使用过STL
* 熟悉一些常用的算法,以及递归等程序设计方法。
此外尽管第一章会介绍一些Template的基本语法但是还是会略显单薄。因此也希望读者能对C++ Template最基本语法形式有所了解和掌握如果会编写基本的模板函数模板类那就更好了。
此外尽管第一章会介绍一些Template的基本语法但是还是会略显单薄。因此也希望读者能对C++ Template最基本语法形式有所了解和掌握如果会编写基本的函数模板模板那就更好了。
诚如上节所述本文并不是《C++ Templates》的简单重复与《Modern C++ Design》交叠更少。从知识结构上我建议大家可以先读本文再阅读《C++ Templates》获取更丰富的语法与实现细节以更进一步《Modern C++ Design》除了元编程之外还有很多的泛型编程示例原则上泛型编程的部分与我所述的内容交叉不大读者在读完1-3章了解模板的基本规则之后便可阅读《MCD》的相应章节元编程部分如Typelist建议在阅读完本文之后再行阅读或许会更易理解。
### 0.3 版权
## 1.3. 版权
本文是随写随即同步到Github上因此在行文中难免会遗漏引用。本文绝大部分内容应是直接承出我笔但是也不定会有他山之石。所有指涉内容我会尽量以引号框记或在上下文和边角注记中标示如有遗漏烦请不吝指出。
全文所有为我所撰写的部分,作者均保留所有版权。如果有需要转帖或引用,还请注明出处并告知于我。
### 0.4 推荐编译环境
## 1.4. 推荐编译环境
C++编译器众多,且对模板的支持可能存在细微差别。如果没有特别强调,本书行文过程中,使用了下列编译器来测试文中提供的代码和示例:
@ -116,9 +98,9 @@ C++编译器众多,且对模板的支持可能存在细微差别。如果没
|---|---|
| std::decay_t<T> | C++ 14 |
### 0.5 体例
## 1.5. 体例
#### 0.5.1 示例代码
### 1.5.1. 示例代码
```C++
void SampleCode() {
@ -126,7 +108,7 @@ void SampleCode() {
}
```
#### 0.5.2 引用
### 1.5.2. 引用
引用自C++标准:
@ -137,7 +119,7 @@ void SampleCode() {
> 《书名》
> 这是一段引用或翻译自其他图书的文字
### 0.6 意见、建议、喷、补遗、写作计划
## 1.6. 意见、建议、喷、补遗、写作计划
* 需增加:
* 模板的使用动机。
@ -150,19 +132,23 @@ void SampleCode() {
* 比较模板和函数的差异性
* 蓝色C++14 Return type deduction for normal functions 的分析
## 1. Template的基本语法
# 2. Template的基本语法
### 1.1 Template Class基本语法
## 2.1. 什么是模板(Template)
#### 1.1.1 Template Class的与成员变量定义
我们来回顾一下最基本的Template Class声明和定义形式
## 2.2. 类模板 (Class Template) 的基本语法
Template Class声明
### 2.2.1. “模板类”还是“类模板”
### 2.2.2. Class Template的与成员变量定义
我们来回顾一下最基本的Class Template声明和定义形式
Class Template声明
```C++
template <typename T> class ClassA;
```
Template Class定义
Class Template定义
```C++
template <typename T> class ClassA
{
@ -180,7 +166,7 @@ void foo(int a);
在定义完模板参数之后,便可以定义你所需要的类。不过在定义类的时候,除了一般类可以使用的类型外,你还可以使用在模板参数中使用的类型 `T`。可以说,这个 `T`是模板的精髓因为你可以通过指定模板实参将T替换成你所需要的类型。
例如我们用`ClassA<int>`来实例化模板ClassA那么`ClassA<int>`可以等同于以下的定义:
例如我们用`ClassA<int>`来实例化模板ClassA那么`ClassA<int>`可以等同于以下的定义:
``` C++
// 注意这并不是有效的C++语法,只是为了说明模板的作用
@ -189,11 +175,11 @@ typedef class {
} ClassA<int>;
```
可以看出通过模板参数替换类型可以获得很多形式相同的新类型有效减少了代码量。这种用法我们称之为“泛型”Generic Programming它最常见的应用即是STL中的容器模板
可以看出通过模板参数替换类型可以获得很多形式相同的新类型有效减少了代码量。这种用法我们称之为“泛型”Generic Programming它最常见的应用即是STL中的容器模板。
#### 1.1.2 模板的使用
### 2.2.3. 模板的使用
对于C++来说,类型最重要的作用之一就是用它去产生一个变量。例如我们定义了一个动态数组(列表)的模板`vector`它对于任意的元素类型都具有push_back和clear的操作我们便可以如下定义这个类
对于C++来说,类型最重要的作用之一就是用它去产生一个变量。例如我们定义了一个动态数组(列表)的模板`vector`它对于任意的元素类型都具有push_back和clear的操作我们便可以如下定义这个类
```C++
template <typename T>
@ -222,14 +208,14 @@ intArray.push_back(5);
floatArray.push_back(3.0f);
```
变量定义的过程可以分成两步来看:第一步,`vector<int>`将`int`绑定到模板`vector`上,获得了一个“普通的类`vector<int>`”第二步通过“vector<int>”定义了一个变量。
与“普通的类”不同,模板是不能直接用来定义变量的。例如:
变量定义的过程可以分成两步来看:第一步,`vector<int>`将`int`绑定到模板`vector`上,获得了一个“普通的类`vector<int>`”第二步通过“vector<int>”定义了一个变量。
与“普通的类”不同,模板是不能直接用来定义变量的 —— 毕竟它的名字是“模板”而不是“类”。例如:
```C++
vector unknownVector; // 错误示例
```
这样就是错误的。我们把通过类型绑定将模板变成“普通的类”的过程称之为模板实例化Template Instantiate。实例化的语法是
这样就是错误的。我们把通过类型绑定将模板变成“普通的类”的过程称之为模板实例化Template Instantiate。实例化的语法是
```
模板名 < 模板实参1 [模板实参2...] >
@ -251,9 +237,9 @@ ClassB<int, float>
当然,在实例化过程中,被绑定到模板参数上的类型(即模板实参)需要与模板形参正确匹配。
就如同函数一样,如果没有提供足够并匹配的参数,模板便不能正确的实例化。
#### 1.1.3 模板类的成员函数定义
### 2.2.4. 类模板的成员函数定义
由于C++11正式废弃“模板导出”这一特性因此在模板的变量在调用成员函数的时候,需要看到完整的成员函数定义。因此现在的模板中的成员函数,通常都是以内联的方式实现。
由于C++11正式废弃“模板导出”这一特性因此在模板的变量在调用成员函数的时候,需要看到完整的成员函数定义。因此现在的模板中的成员函数,通常都是以内联的方式实现。
例如:
``` C++
@ -314,11 +300,11 @@ void vector<T> /*看起来像偏特化*/ ::clear() // 函数的实现放在这
}
```
### 1.2 Template Function的基本语法
## 2.3. 函数模板 (Function Tempalte) 入门
#### 1.2.1 Template Function的声明和定义
### 2.3.1. 函数模板的声明和定义
模板函数的语法与模板基本相同,也是以关键字`template`和模板参数列表作为声明与定义的开始。模板参数列表中的类型,可以出现在参数、返回值以及函数体中。比方说下面几个例子
函数模板的语法与模板基本相同,也是以关键字`template`和模板参数列表作为声明与定义的开始。模板参数列表中的类型,可以出现在参数、返回值以及函数体中。比方说下面几个例子
```C++
template <typename T> void foo(T const& v);
@ -356,7 +342,7 @@ template <typename T> void foo()
举个例子generic typed function add
```
在我遇到的朋友中,即便如此对他解释了模板,即便他了解了模板,也仍然会对模板产生畏难情绪。毕竟从形式上来说,模板类和模板函数都要较非模板的版本更加复杂,阅读代码所需要理解的内容也有所增多。
在我遇到的朋友中,即便如此对他解释了模板,即便他了解了模板,也仍然会对模板产生畏难情绪。毕竟从形式上来说,模板化的类和模板化的函数都要较非模板的版本更加复杂,阅读代码所需要理解的内容也有所增多。
如何才能克服这一问题,最终视模板如平坦代码呢?
@ -370,7 +356,7 @@ template <typename T> void foo()
3. 把解决方案用代码写出来。
4. 如果失败了,找到原因。是知识有盲点(例如不知道怎么将 `T&` 转化成 `T`),还是不可行(比如试图利用浮点常量特化模板,但实际上这样做是不可行的)?
4. 如果失败了,找到原因。是知识有盲点(例如不知道怎么将 `T&` 转化成 `T`),还是不可行(比如试图利用浮点常量特化模板,但实际上这样做是不可行的)?
通过重复以上的练习,应该可以对模板的语法和含义都有所掌握。如果提出问题本身有困难,或许下面这个经典案例可以作为你思考的开始:
@ -380,7 +366,7 @@ template <typename T> void foo()
当然和“设计模式”一样模板在实际应用中也会有一些固定的需求和解决方案。比较常见的场景包括泛型最基本的用法、通过类型获得相应的信息型别萃取、编译期间的计算、类型间的推导和变换从一个类型变换成另外一个类型比如boost::function。这些本文在以后的章节中会陆续介绍。
#### 1.2.2 模板函数的使用
### 2.3.2. 函数模板的使用
我们先来看一个简单的函数模板,两个数相加:
@ -479,7 +465,7 @@ int b = GetValue<int>(1);
是不是so easy啊你又信心满满的做了一个练习
你要写一个模板函数`c_style_cast`顾名思义执行的是C风格的转换。然后出于方便起见你希望它能和 `static_cast` 这样的内置转换有同样的写法。于是你写了一个use case。
你要写一个函数模板叫 `c_style_cast`顾名思义执行的是C风格的转换。然后出于方便起见你希望它能和 `static_cast` 这样的内置转换有同样的写法。于是你写了一个use case。
``` C++
DstT dest = c_style_cast<DstT>(src);
@ -527,7 +513,7 @@ int v = 0;
float i = c_style_cast<float>(v); // 形象地说DstT会先把你指定的参数吃掉剩下的就交给编译器从函数参数列表中推导啦。
```
### 1.3 整型也可是Template参数
## 2.4. 整型也可是Template参数
模板参数除了类型外包括基本类型、结构、类类型等也可以是一个整型数Integral Number。这里的整型数比较宽泛包括布尔型不同位数、有无符号的整型甚至包括指针。我们将整型的模板参数和类型作为模板参数来做一个对比
@ -607,20 +593,20 @@ template <float a> class E {}; // ERROR: 别闹!早说过只能是整数类型
当然除了单纯的用作常数之外整型参数还有一些其它的用途。这些“其它”用途最重要的一点是让类型也可以像整数一样运算。《Modern C++ Design》给我们展示了很多这方面的例子。不过你不用急着去阅读那本天书我们会在做好足够的知识铺垫后让你轻松学会这些招数。
### 1.4 模板形式与功能是统一的
## 2.5. 模板形式与功能是统一的
第一章走马观花的带着大家复习了一下C++ Template的基本语法形式也解释了包括 `typename` 在内,类/函数模板写法中各个语法元素的含义。形式是功能的外在体现,介绍它们也是为了让大家能理解到,模板之所以写成这种形式是有必要的,而不是语言的垃圾成分。
从下一章开始,我们便进入了更加复杂和丰富的世界:讨论模板的匹配规则。其中有令人望而生畏的特化与偏特化。但是,请相信我们在序言中所提到的:将模板作为一门语言来看待,它会变得有趣而简单。
## 2. 模板元编程基础
### 2.1 编程,元编程,模板元编程
# 3. 模板元编程基础
## 3.1. 编程,元编程,模板元编程
技术的学习是一个登山的过程。第一章是最为平坦的山脚道路。而从这一章开始,则是正式的爬坡。无论是我写作还是你阅读,都需要付出比第一章更多的代价。那么问题就是,付出更多的精力学习模板是否值得?
这个问题很功利但是一针见血。因为技术的根本目的在于解决需求。那C++的模板能做什么?
一个高的回答是C++里面的模板犹如C中的宏、C#和Java中的自省restropection和反射reflection是一个改变语言内涵拓展语言外延的存在。
一个高的回答是C++里面的模板犹如C中的宏、C和Java中的自省restropection和反射reflection是一个改变语言内涵拓展语言外延的存在。
程序最根本的目的是什么?复现真实世界或人所构想的规律,减少重复工作的成本,或通过提升规模完成人所不能及之事。但是世间之事万千,有限的程序如何重现复杂的世界呢?
@ -761,9 +747,9 @@ for(v4a, v4b : vectorsA, vectorsB)
好吧,我承认这个例子还是太牵强了。不过相信我,在你阅读完第二章和第三章之后,你会将这些特性自如地运用到你的程序之中。你的程序将会变成体现模板“可运算”威力的最好例子。
### 2.2 模板世界的If-Then-Else类模板的特化与偏特化
## 3.2. 模板世界的If-Then-Else类模板的特化与偏特化
#### 2.2.1 根据类型执行代码
### 3.2.1. 根据类型执行代码
前一节的示例提出了一个要求需要做出根据类型执行不同代码。要达成这一目的模板并不是唯一的途径。比如之前我们所说的重载。如果把眼界放宽一些虚函数也是根据类型执行代码的例子。此外在C语言时代也会有一些技法来达到这个目的比如下面这个例子我们需要对两个浮点做加法 或者对两个整数做乘法:
``` C
@ -797,7 +783,7 @@ Variant addFloatOrMulInt(Variant const* a, Variant const* b)
更常见的是 `void*`:
``` C++
#define BIN_OP(type, a, op, b, result) (*(type *)(result)) = (*(type const *)(a)) op (*(type const*)(b))
define BIN_OP(type, a, op, b, result) (*(type *)(result)) = (*(type const *)(a)) op (*(type const*)(b))
void doDiv(void* out, void const* data0, void const* data1, DATA_TYPE type)
{
if(type == TYPE_INT)
@ -868,7 +854,7 @@ Variant result = addFloatOrMulInt(aVar, bVar);
在模板代码中这个“合适的机制”就是指“特化”和“部分特化Partial Specialization后者也叫“偏特化”。
#### 2.2.2 特化
### 3.2.2. 特化
我的高中物理老师对我说过一句令我受用至今的话:把自己能做的事情做好。编写模板程序也是一样。当你试图用模板解决问题之前,先撇开那些复杂的语法要素,用最直观的方式表达你的需求:
@ -1087,7 +1073,7 @@ void PrintID()
如果你体味到了这一点,那么恭喜你,你的模板元编程已经开悟了。
#### 2.2.3 特化:一些其它问题
### 3.2.3. 特化:一些其它问题
在上一节结束之后,你一定做了许多的练习。我们再来做三个练习。第一,给`float`一个ID第二给`void*`一个ID第三给任意类型的指针一个ID。先来做第一个:
@ -1165,7 +1151,7 @@ void PrintID()
{
cout << "ID of float: " << TypeToID<float>::ID << endl; // Print "1"
cout << "NotID of float: " << TypeToID<float>::NotID << endl; // Error! TypeToID<float>使用的特化的类这个类的实现没有NotID这个成员。
cout << "ID of double: " << TypeToID<double>::ID << endl; // Error! TypeToID<double>是由模板实例化出来的它只有NotID没有ID这个成员。
cout << "ID of double: " << TypeToID<double>::ID << endl; // Error! TypeToID<double>是由模板实例化出来的它只有NotID没有ID这个成员。
}
```
@ -1352,9 +1338,9 @@ void PrintID()
但是呢,直觉对付更加复杂的问题还是没用的(也不是没用,主要是你没这个直觉了)。我们要把这个直觉,转换成合理的规则——即模板的匹配规则。
当然,这个匹配规则是对复杂问题用的,所以我们会到实在一眼看不出来的时候才会动用它。一开始我们只要把握:**模板是从最特殊到最一般形式进行匹配的** 就可以了。
### 2.3 即用即推导
## 3.3. 即用即推导
#### 2.3.1 视若无睹的语法错误
### 3.3.1. 视若无睹的语法错误
这一节我们将讲述模板一个非常重要的行为特点:那就是什么时候编译器会对模板进行推导,推导到什么程度。
这一知识,对于理解模板的编译期行为、以及修正模板编译错误都非常重要。
@ -1390,7 +1376,7 @@ template <typename T> struct Y
这时我们就需要请出C++11标准 —— 中的某些概念了。这是我们到目前为止第一次参阅标准。我希望能尽量减少直接参阅标准的次数,因此即便是极为复杂的模板匹配决议我都暂时没有引入标准中的描述。
然而Template引入的“双阶段名称查找Two phase name lookup”堪称是C++中最黑暗的角落 —— 这是LLVM的团队自己在博客上说的 —— 因此在这里,我们还是有必要去了解标准中是如何规定的。
#### 2.3.2 名称查找I am who I am
### 3.3.2. 名称查找I am who I am
在C++标准中对于“名称查找name lookup”这个高大上的名词的诠释主要集中出现在三处。第一处是3.4节标题名就叫“Name Lookup”第二处在10.2节继承关系中的名称查找第三处在14.6节名称解析name resolution
名称查找/名称解析是编译器的基石。对编译原理稍有了解的人都知道“符号表”的存在及重要意义。考虑一段最基本的C代码
@ -1505,7 +1491,7 @@ template <typename T> struct X {
接下来我们就来解决2.3.1节中留下的几个问题。
先看第四个问题。为什么MSVC中模板函数的定义内不管填什么编译器都不报错因为MSVC在分析模板中成员函数定义时没有做任何事情。至于为啥连“大王叫我来巡山”都能过得去这是C++语法/语义分析的特殊性导致的。
先看第四个问题。为什么MSVC中函数模板的定义内不管填什么编译器都不报错因为MSVC在分析模板中成员函数定义时没有做任何事情。至于为啥连“大王叫我来巡山”都能过得去这是C++语法/语义分析的特殊性导致的。
C++是个非常复杂的语言,以至于它的编译器,不可能通过词法-语法-语义多趟分析清晰分割,因为它的语义将会直接干扰到语法:
```C++
@ -1625,7 +1611,7 @@ error: variable has incomplete type 'A'
1 error generated.
```
符合标准的写法需要将模板的定义,和模板函数的定义分离开:
符合标准的写法需要将模板的定义,和函数模板的定义分离开:
> TODO 此处例子不够恰当,并且描述有歧义。需要在未来版本中修订。
@ -1648,11 +1634,11 @@ void main() {
}
```
但是其实我们知道,`foo`要到实例化之后才需要真正的做语义分析。在MSVC上因为函数实现就是到模板实例化时才处理的所以这个例子是完全正常工作的。因此在上面这个例子中MSVC的实现要比标准更加易于写和维护是不是有点写Java/C#那种声明实现都在同一处的清爽感觉了呢!
但是其实我们知道,`foo`要到实例化之后才需要真正的做语义分析。在MSVC上因为函数实现就是到模板实例化时才处理的所以这个例子是完全正常工作的。因此在上面这个例子中MSVC的实现要比标准更加易于写和维护是不是有点写Java/C那种声明实现都在同一处的清爽感觉了呢
扩展阅读: [The Dreaded Two-Phase Name Lookup][2]
#### 2.3.3 “多余的” typename 关键字
### 3.3.3. “多余的” typename 关键字
到了这里2.3.1 中提到的四个问题,还有三个没有解决:
@ -1743,7 +1729,7 @@ template <typename T> struct X {
};
```
### 2.4 本章小结
## 3.4. 本章小结
这一章是写作中最艰难的一章中间停滞了将近一年。因为要说清楚C++模板中一些语法噪音和设计决议并不是一件轻松的事情。不过通过这一章的学习,我们知道了下面这几件事情:
@ -1755,11 +1741,11 @@ template <typename T> struct X {
从下一章开始,我们将进入元编程环节。我们将使用大量的示例,一方面帮助巩固大家学到的模板知识,一方面也会引导大家使用函数式思维去解决常见的问题。
## 3 深入理解特化与偏特化
# 4. 深入理解特化与偏特化
### 3.1 正确的理解偏特化
## 4.1. 正确的理解偏特化
#### 3.1.1 偏特化与函数重载的比较
### 4.1.1. 偏特化与函数重载的比较
在前面的章节中,我们介绍了偏特化的形式、也介绍了简单的用例。因为偏特化和函数重载存在着形式上的相似性,因此初学者便会借用重载的概念,来理解偏特化的行为。只是,重载和偏特化尽管相似但仍有差异。
@ -1913,7 +1899,7 @@ X<double*, double> v8;
其他的示例可以先自己推测一下, 再去编译器上尝试一番:[`goo.gl/9UVzje`](http://goo.gl/9UVzje)。
#### 3.1.2 不定长的模板参数
### 4.1.2. 不定长的模板参数
不过这个时候也许你还不死心。有没有一种办法能够让例子`DoWork`像重载一样,支持对长度不一的参数列表分别偏特化/特化呢?
@ -2032,7 +2018,7 @@ template <typename... Ts, typename U> class Y<Ts..., U> {}; // (4) error!
在这里,我们只提到了变长模板参数的声明,如何使用我们将在第四章讲述。
#### 3.1.3 模板的默认实参
### 4.1.3. 模板的默认实参
在上一节中,我们介绍了模板对默认实参的支持。当时我们的例子很简单,默认模板实参是一个确定的类型`void`或者自定义的`null_type`
@ -2049,7 +2035,7 @@ template <
第一步,我们先把浮点正确的写出来:
```C++
#include <type_traits>
include <type_traits>
template <typename T> T CustomDiv(T lhs, T rhs) {
// Custom Div的实现
@ -2084,8 +2070,8 @@ void foo(){
嗯,这个时候我们要再把整型和其他类型纳入进来,无外乎就是加这么一个参数[`goo.gl/0Lqywt`](http://goo.gl/0Lqywt)
```C++
#include <complex>
#include <type_traits>
include <complex>
include <type_traits>
template <typename T> T CustomDiv(T lhs, T rhs) {
T v;
@ -2125,8 +2111,8 @@ void foo(){
当然,这时也许你会注意到,`is_integral``is_floating_point`和其他类类型三者是互斥的,那能不能只使用一个条件量来进行分派呢?答案当然是可以的:[`goo.gl/jYp5J2`](http://goo.gl/jYp5J2)
```cpp
#include <complex>
#include <type_traits>
include <complex>
include <type_traits>
template <typename T> T CustomDiv(T lhs, T rhs) {
T v;
@ -2179,7 +2165,7 @@ void foo(){
* A和B都与模板实参无法匹配所以使用原型调用`CustomDiv`
### 3.2 后悔药SFINAE
## 4.2. 后悔药SFINAE
考虑下面这个函数模板:
@ -2477,11 +2463,11 @@ void inc_counter(ICounter& counterObj);
嗯,你说的没错,在这里这个特性一点都没用。
这也提醒我们,当你觉得需要写 `enable_if` 的时候,首先要考虑到以下可能
这也提醒我们,当你觉得需要写 `enable_if` 的时候,首先要考虑到以下可能的替代方案
* 重载(对模板函数
* 重载(适用于函数模板
* 偏特化(对模板类而言
* 偏特化(适用于类模板
* 虚函数
@ -2525,9 +2511,9 @@ void doSomething() {
```C++
#include <type_traits>
#include <utility>
#include <cstdint>
include <type_traits>
include <utility>
include <cstdint>
struct ICounter {};
struct Counter: public ICounter {
@ -2632,17 +2618,18 @@ void foo(
虽然它写起来并不直观但是对于既没有编译器自省、也没有Concept的C++11来说已经是最好的选择了。
### Concept “概念”
## 4.3. Concept “概念”:对模板参数约束的直接描述
#### “概念” 解决了什么问题
### 4.3.1. “概念” 解决了什么问题
从上一节可以看出,我们兜兜转转了那么久,就是为了解决两个问题:
1. 在模板进行特化的时候,盘算一下并告诉编译器这里能不能特化;
2. 在函数决议面临多个候选的时候,如果有且仅有其中一个原型能够被函数决议接纳,那就决定是你了!
如果能够直接表达给编译器不就不用这么麻烦了么。其实在很多现代语言中都有类似的语言要素存在比如C#的约束constraint on type parameters)
``` C#
如果语言能允许用户直接描述需求并传达给编译器不就不用这么麻烦了么。其实在很多现代语言中都有类似的语言要素存在比如C的约束constraint on type parameters)
``` C
public class Employee {
// ...
}
@ -2685,15 +2672,15 @@ void inc_counter(T& intTypeCounter) {
```
直接告诉编译器我们对T的要求是你得有`++`。
当然有人会问,那能不能直接写成以下形式,不是更简单吗
当然有人会问,那能不能直接写成以下形式,不是更简单吗
``` C++
template <typename T> requires (T t) { ++t; }
void inc_counter(T& cnt);
```
答案是不能。
因为`requires`作为keyword是存在二义性的。当它用于模板函数或者模板类的声明时它是一个constraint后面需要跟着concept表达式而用于concept中则是一个required expression用于concept的求解。既然constraint后面跟着一个concept表达式而requires也可以用来定义一个concept expression那么一个风骚的想法形成了我能不能用 `requires (requires (T t) {++t;})` 来约束模板函数的类型呢?
答案是不能。
因为`requires`作为关键字/保留字是存在二义性的。当它用于函数模板或者类模板的声明时它是一个constraint后面需要跟着concept表达式而用于concept中则是一个required expression用于concept的求解。既然constraint后面跟着一个concept表达式而requires也可以用来定义一个concept expression那么一个风骚的想法形成了我能不能用 `requires (requires (T t) {++t;})` 来约束模板参数呢?
当然是可以的C++就是这么的简(~~有~~)单(~~病~~
@ -2704,7 +2691,8 @@ void inc_counter(T& cnt);
总而言之,除了这些烦人的问题,“概念”的出现,使得模板的出错提示也清爽了些许 —— 虽然大佬们都在鼓吹concept让模板出错多么好调试但是实际上模板出错有一半是来源自类型系统本质上的复杂性概念并不能解决这一问题。
比如这里使用SFINAE的提示
比如这里使用SFINAE的提示
```
<source>:23:5: error: no matching function for call to 'Inc'
Inc(y);
@ -2729,44 +2717,48 @@ template <Incrementable T>
concept Incrementable = requires(T t) { ++t; };
```
虽然看起来要更长一点,但是对于复杂类型来说,还是会友善许多。以后会找个例子给大家陈述。
虽然在这个例子中,通过 *Concept* 获得出错提示看起来要比使用 *SFINAE* 所获得的错误描述要更长一点,但是对于更加复杂类型来说,则会友善许多。以后会找个例子给大家陈述。
### 4.3.2. "概念"入门
## !!! 以下章节未完成 !!!
# 5. 未完成章节
## 4 元编程下的数据结构与算法
### 4.1 表达式与数值计算
### 4.1 获得类型的属性——类型萃取Type Traits
### 4.2 列表与数组
### 4.3 字典结构
### 4.4 “快速”排序
### 4.5 其它常用的“轮子”
```
# 6. 元编程下的数据结构与算法
## 6.1. 表达式与数值计算
## 6.2. 获得类型的属性——类型萃取Type Traits
## 6.3. 列表与数组
## 6.4. 字典结构
## 6.5. “快速”排序
## 6.6. 其它常用的“轮子”
## 非模板的编译期计算
# 7. 非模板的编译期计算
## 5 模板的进阶技巧
### 5.1 嵌入类
### 5.2 Template-Template Class
### 5.3 高阶函数
### 5.4 闭包:模板的“基于对象”
# 8. 模板的进阶技巧
## 8.1. 嵌入类
## 8.2. Template-Template Class
## 8.3. 高阶函数
## 8.4. 闭包:模板的“基于对象”
stl allocator?
mpl::apply
### 5.5 占位符(placeholder)在C++中实现方言的基石
### 5.6 编译期“多态”
## 8.5. 占位符(placeholder)在C++中实现方言的基石
## 8.6. 编译期“多态”
## 6 模板的威力从foreach, transform到Linq
### 6.1 Foreach与Transform
### 6.2 Boost中的模板
# 9. 模板的威力从foreach, transform到Linq
## 9.1. Foreach与Transform
## 9.2. Boost中的模板
Any Spirit Hana TypeErasure
### 6.3 Reactor、Linq与C++中的实践
### 6.4 更高更快更强从Linq到FP
## 9.3. Reactor、Linq与C++中的实践
## 9.4. 更高更快更强从Linq到FP
## 7 结语:讨论有益,争端无用
### 7.1 更好的编译器,更友善的出错信息
### 7.2 模板的症结:易于实现,难于完美
### 7.3 一些期望
# 10. 结语:讨论有益,争端无用
## 10.1. 更好的编译器,更友善的出错信息
## 10.2. 模板的症结:易于实现,难于完美
## 10.3. 一些期望
alexandrescu 关于 min max 的讨论《再谈Min和Max》
std::experimental::any / boost.any 对于 reference 的处理
```
[1]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf