diff --git a/ReadMe.md b/ReadMe.md index 13f422c..c6c0cd1 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,71 +1,53 @@ -# C++ Template 进阶指南 + C++ Template 进阶指南 + ================= 章节目录由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. Function Template 的声明和定义](#231-function-template-的声明和定义) + - [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++入门,例子中也多少会牵涉一些其它知识,因此如果读者能够具备以下条件,会读起来更加轻松: @@ -95,13 +77,13 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言, 诚如上节所述,本文并不是《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 | 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 class ClassA; ``` -Template Class定义: +Class Template定义: ```C++ template class ClassA { @@ -191,7 +177,7 @@ typedef class { 可以看出,通过模板参数替换类型,可以获得很多形式相同的新类型,有效减少了代码量。这种用法,我们称之为“泛型”(Generic Programming),它最常见的应用,即是STL中的容器模板类。 -#### 1.1.2 模板的使用 +### 2.2.3. 模板的使用 对于C++来说,类型最重要的作用之一就是用它去产生一个变量。例如我们定义了一个动态数组(列表)的模板类`vector`,它对于任意的元素类型都具有push_back和clear的操作,我们便可以如下定义这个类: @@ -251,7 +237,7 @@ ClassB 当然,在实例化过程中,被绑定到模板参数上的类型(即模板实参)需要与模板形参正确匹配。 就如同函数一样,如果没有提供足够并匹配的参数,模板便不能正确的实例化。 -#### 1.1.3 模板类的成员函数定义 +### 2.2.4. 模板类的成员函数定义 由于C++11正式废弃“模板导出”这一特性,因此在模板类的变量在调用成员函数的时候,需要看到完整的成员函数定义。因此现在的模板类中的成员函数,通常都是以内联的方式实现。 例如: @@ -314,9 +300,9 @@ void vector /*看起来像偏特化*/ ::clear() // 函数的实现放在这 } ``` -### 1.2 Template Function的基本语法 +## 2.3. 函数模板 (Function Tempalte) 入门 -#### 1.2.1 Template Function的声明和定义 +### 2.3.1. Function Template 的声明和定义 模板函数的语法与模板类基本相同,也是以关键字`template`和模板参数列表作为声明与定义的开始。模板参数列表中的类型,可以出现在参数、返回值以及函数体中。比方说下面几个例子 @@ -380,7 +366,7 @@ template void foo() 当然和“设计模式”一样,模板在实际应用中,也会有一些固定的需求和解决方案。比较常见的场景包括:泛型(最基本的用法)、通过类型获得相应的信息(型别萃取)、编译期间的计算、类型间的推导和变换(从一个类型变换成另外一个类型,比如boost::function)。这些本文在以后的章节中会陆续介绍。 -#### 1.2.2 模板函数的使用 +### 2.3.2. 模板函数的使用 我们先来看一个简单的函数模板,两个数相加: @@ -527,7 +513,7 @@ int v = 0; float i = c_style_cast(v); // 形象地说,DstT会先把你指定的参数吃掉,剩下的就交给编译器从函数参数列表中推导啦。 ``` -### 1.3 整型也可是Template参数 +## 2.4. 整型也可是Template参数 模板参数除了类型外(包括基本类型、结构、类类型等),也可以是一个整型数(Integral Number)。这里的整型数比较宽泛,包括布尔型,不同位数、有无符号的整型,甚至包括指针。我们将整型的模板参数和类型作为模板参数来做一个对比: @@ -607,20 +593,20 @@ template 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。先来做第一个: @@ -1352,9 +1338,9 @@ void PrintID() 但是呢,直觉对付更加复杂的问题还是没用的(也不是没用,主要是你没这个直觉了)。我们要把这个直觉,转换成合理的规则——即模板的匹配规则。 当然,这个匹配规则是对复杂问题用的,所以我们会到实在一眼看不出来的时候才会动用它。一开始我们只要把握:**模板是从最特殊到最一般形式进行匹配的** 就可以了。 -### 2.3 即用即推导 +## 3.3. 即用即推导 -#### 2.3.1 视若无睹的语法错误 +### 3.3.1. 视若无睹的语法错误 这一节我们将讲述模板一个非常重要的行为特点:那就是什么时候编译器会对模板进行推导,推导到什么程度。 这一知识,对于理解模板的编译期行为、以及修正模板编译错误都非常重要。 @@ -1390,7 +1376,7 @@ template 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代码: @@ -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 struct X { }; ``` -### 2.4 本章小结 +## 3.4. 本章小结 这一章是写作中最艰难的一章,中间停滞了将近一年。因为要说清楚C++模板中一些语法噪音和设计决议并不是一件轻松的事情。不过通过这一章的学习,我们知道了下面这几件事情: @@ -1755,11 +1741,11 @@ template struct X { 从下一章开始,我们将进入元编程环节。我们将使用大量的示例,一方面帮助巩固大家学到的模板知识,一方面也会引导大家使用函数式思维去解决常见的问题。 -## 3 深入理解特化与偏特化 +# 4. 深入理解特化与偏特化 -### 3.1 正确的理解偏特化 +## 4.1. 正确的理解偏特化 -#### 3.1.1 偏特化与函数重载的比较 +### 4.1.1. 偏特化与函数重载的比较 在前面的章节中,我们介绍了偏特化的形式、也介绍了简单的用例。因为偏特化和函数重载存在着形式上的相似性,因此初学者便会借用重载的概念,来理解偏特化的行为。只是,重载和偏特化尽管相似但仍有差异。 @@ -1913,7 +1899,7 @@ X v8; 其他的示例可以先自己推测一下, 再去编译器上尝试一番:[`goo.gl/9UVzje`](http://goo.gl/9UVzje)。 -#### 3.1.2 不定长的模板参数 +### 4.1.2. 不定长的模板参数 不过这个时候也许你还不死心。有没有一种办法能够让例子`DoWork`像重载一样,支持对长度不一的参数列表分别偏特化/特化呢? @@ -2032,7 +2018,7 @@ template class Y {}; // (4) error! 在这里,我们只提到了变长模板参数的声明,如何使用我们将在第四章讲述。 -#### 3.1.3 模板的默认实参 +### 4.1.3. 模板的默认实参 在上一节中,我们介绍了模板对默认实参的支持。当时我们的例子很简单,默认模板实参是一个确定的类型`void`或者自定义的`null_type`: @@ -2049,7 +2035,7 @@ template < 第一步,我们先把浮点正确的写出来: ```C++ -#include +include template T CustomDiv(T lhs, T rhs) { // Custom Div的实现 @@ -2084,8 +2070,8 @@ void foo(){ 嗯,这个时候我们要再把整型和其他类型纳入进来,无外乎就是加这么一个参数[`goo.gl/0Lqywt`](http://goo.gl/0Lqywt): ```C++ -#include -#include +include +include template 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 -#include +include +include template T CustomDiv(T lhs, T rhs) { T v; @@ -2179,7 +2165,7 @@ void foo(){ * A和B都与模板实参无法匹配,所以使用原型,调用`CustomDiv` -### 3.2 后悔药:SFINAE +## 4.2. 后悔药:SFINAE 考虑下面这个函数模板: @@ -2525,9 +2511,9 @@ void doSomething() { ```C++ -#include -#include -#include +include +include +include 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 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的提示: + ``` :23:5: error: no matching function for call to 'Inc' Inc(y); @@ -2729,44 +2717,48 @@ template 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