diff --git a/ReadMe.md b/ReadMe.md index 1f4c65a..8a85d9b 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) - -## 0. 前言 - -### 0.1 C++另类简介:比你用的复杂,但比你想的简单 +- [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 Template) 入门](#23-函数模板-function-template-入门) + - [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-未完成章节) + +# 1. 前言 + +## 1.1. C++另类简介:比你用的复杂,但比你想的简单 C++似乎从它为世人所知的那天开始便成为天然的话题性编程语言。在它在周围有着形形色色的赞美与贬低之词。当我在微博上透露欲写此文的意愿时,也收到了很多褒贬不一的评论。作为一门语言,能拥有这么多使用并恨着它、使用并畏惧它的用户,也算是语言丛林里的奇观了。 @@ -83,7 +65,7 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言, 本文的写作初衷,就是通过"编程语言"的视角,介绍一个简单、清晰的"模板语言"。我会尽可能地将模板的诸多要素连串起来,用一些简单的例子帮助读者学习这门"语言",让读者在编写、阅读模板代码的时候,能像 `if(exp) { dosomething(); }`一样的信手拈来,让"模板元编程"技术成为读者牢固掌握、可举一反三的有用技能。 -### 0.2 适宜读者群 +## 1.2. 适宜读者群 因为本文并不是用于C++入门,例子中也多少会牵涉一些其它知识,因此如果读者能够具备以下条件,会读起来更加轻松: @@ -91,23 +73,22 @@ 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++编译器众多,且对模板的支持可能存在细微差别。如果没有特别强调,本书行文过程中,使用了下列编译器来测试文中提供的代码和示例: -* Clang 3.7 (x86) -* Visual Studio 2015 Update 3 -* GCC 7 (x86, snapshot) +* Clang 14.0.3; 15.0 (amd64) +* Visual Studio 2022 19.2+ (amd64) 此外,部分复杂实例我们还在文中提供了在线的编译器预览以方便大家阅读和测试。在线编译器参见: [`gcc.godbolt.org`](https://gcc.godbolt.org/)。 @@ -117,9 +98,9 @@ C++编译器众多,且对模板的支持可能存在细微差别。如果没 |---|---| | std::decay_t | C++ 14 | -### 0.5 体例 +## 1.5. 体例 -#### 0.5.1 示例代码 +### 1.5.1. 示例代码 ```C++ void SampleCode() { @@ -127,7 +108,7 @@ void SampleCode() { } ``` -#### 0.5.2 引用 +### 1.5.2. 引用 引用自C++标准: @@ -138,7 +119,7 @@ void SampleCode() { > 《书名》 > 这是一段引用或翻译自其他图书的文字 -### 0.6 意见、建议、喷、补遗、写作计划 +## 1.6. 意见、建议、喷、补遗、写作计划 * 需增加: * 模板的使用动机。 @@ -151,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 { @@ -181,7 +166,7 @@ void foo(int a); 在定义完模板参数之后,便可以定义你所需要的类。不过在定义类的时候,除了一般类可以使用的类型外,你还可以使用在模板参数中使用的类型 `T`。可以说,这个 `T`是模板的精髓,因为你可以通过指定模板实参,将T替换成你所需要的类型。 -例如我们用`ClassA`来实例化模板类ClassA,那么`ClassA`可以等同于以下的定义: +例如我们用`ClassA`来实例化类模板ClassA,那么`ClassA`可以等同于以下的定义: ``` C++ // 注意:这并不是有效的C++语法,只是为了说明模板的作用 @@ -190,11 +175,11 @@ typedef class { } ClassA; ``` -可以看出,通过模板参数替换类型,可以获得很多形式相同的新类型,有效减少了代码量。这种用法,我们称之为"泛型"(Generic Programming),它最常见的应用,即是STL中的容器模板类。 +可以看出,通过模板参数替换类型,可以获得很多形式相同的新类型,有效减少了代码量。这种用法,我们称之为"泛型"(Generic Programming),它最常见的应用,即是STL中的容器类模板。 -#### 1.1.2 模板的使用 +### 2.2.3. 模板的使用 -对于C++来说,类型最重要的作用之一就是用它去产生一个变量。例如我们定义了一个动态数组(列表)的模板类`vector`,它对于任意的元素类型都具有push_back和clear的操作,我们便可以如下定义这个类: +对于C++来说,类型最重要的作用之一就是用它去产生一个变量。例如我们定义了一个动态数组(列表)的类模板`vector`,它对于任意的元素类型都具有push_back和clear的操作,我们便可以如下定义这个类: ```C++ template @@ -223,17 +208,17 @@ intArray.push_back(5); floatArray.push_back(3.0f); ``` -变量定义的过程可以分成两步来看:第一步,`vector`将`int`绑定到模板类`vector`上,获得了一个"普通的类`vector`";第二步通过"vector"定义了一个变量。 -与"普通的类"不同,模板类是不能直接用来定义变量的。例如: +变量定义的过程可以分成两步来看:第一步,`vector`将`int`绑定到类模板`vector`上,获得了一个"普通的类`vector`";第二步通过"vector"定义了一个变量。 +与"普通的类"不同,类模板是不能直接用来定义变量的 —— 毕竟它的名字是"模板"而不是"类"。例如: ```C++ vector unknownVector; // 错误示例 ``` -这样就是错误的。我们把通过类型绑定将模板类变成"普通的类"的过程,称之为模板实例化(Template Instantiate)。实例化的语法是: +这样就是错误的。我们把通过类型绑定将类模板变成"普通的类"的过程,称之为模板实例化(Template Instantiate)。实例化的语法是: ``` -模板名 < 模板实参1 [,模板实参2,...]> +模板名 < [模板实参1,模板实参2,...]> ``` 看几个例子: @@ -252,9 +237,9 @@ ClassB 当然,在实例化过程中,被绑定到模板参数上的类型(即模板实参)需要与模板形参正确匹配。 就如同函数一样,如果没有提供足够并匹配的参数,模板便不能正确的实例化。 -#### 1.1.3 模板类的成员函数定义 +### 2.2.4. 类模板的成员函数定义 -由于C++11正式废弃"模板导出"这一特性,因此在模板类的变量在调用成员函数的时候,需要看到完整的成员函数定义。因此现在的模板类中的成员函数,通常都是以内联的方式实现。 +由于C++11正式废弃"模板导出"这一特性,因此在类模板的变量在调用成员函数的时候,需要看到完整的成员函数定义。因此现在的类模板中的成员函数,通常都是以内联的方式实现。 例如: ``` C++ @@ -315,11 +300,11 @@ void vector /*看起来像偏特化*/ ::clear() // 函数的实现放在这 } ``` -### 1.2 Template Function的基本语法 +## 2.3. 函数模板 (Function Template) 入门 -#### 1.2.1 Template Function的声明和定义 +### 2.3.1. 函数模板的声明和定义 -模板函数的语法与模板类基本相同,也是以关键字`template`和模板参数列表作为声明与定义的开始。模板参数列表中的类型,可以出现在参数、返回值以及函数体中。比方说下面几个例子 +函数模板的语法与类模板基本相同,也是以关键字`template`和模板参数列表作为声明与定义的开始。模板参数列表中的类型,可以出现在参数、返回值以及函数体中。比方说下面几个例子 ```C++ template void foo(T const& v); @@ -357,7 +342,7 @@ template void foo() 举个例子:generic typed function ‘add’ ``` -在我遇到的朋友中,即便如此对他解释了模板,即便他了解了模板,也仍然会对模板产生畏难情绪。毕竟从形式上来说,模板类和模板函数都要较非模板的版本更加复杂,阅读代码所需要理解的内容也有所增多。 +在我遇到的朋友中,即便如此对他解释了模板,即便他了解了模板,也仍然会对模板产生畏难情绪。毕竟从形式上来说,模板化的类和模板化的函数都要较非模板的版本更加复杂,阅读代码所需要理解的内容也有所增多。 如何才能克服这一问题,最终视模板如平坦代码呢? @@ -371,7 +356,7 @@ template void foo() 3. 把解决方案用代码写出来。 - 4. 如果失败了,找到原因。是知识有盲点(例如不知道怎么将 `T&` 转化成 `T`),还是不可行(比如试图利用浮点常量特化模板类,但实际上这样做是不可行的)? + 4. 如果失败了,找到原因。是知识有盲点(例如不知道怎么将 `T&` 转化成 `T`),还是不可行(比如试图利用浮点常量特化类模板,但实际上这样做是不可行的)? 通过重复以上的练习,应该可以对模板的语法和含义都有所掌握。如果提出问题本身有困难,或许下面这个经典案例可以作为你思考的开始: @@ -381,7 +366,7 @@ template void foo() 当然和"设计模式"一样,模板在实际应用中,也会有一些固定的需求和解决方案。比较常见的场景包括:泛型(最基本的用法)、通过类型获得相应的信息(型别萃取)、编译期间的计算、类型间的推导和变换(从一个类型变换成另外一个类型,比如boost::function)。这些本文在以后的章节中会陆续介绍。 -#### 1.2.2 模板函数的使用 +### 2.3.2. 函数模板的使用 我们先来看一个简单的函数模板,两个数相加: @@ -480,7 +465,7 @@ int b = GetValue(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(src); @@ -528,7 +513,7 @@ int v = 0; float i = c_style_cast(v); // 形象地说,DstT会先把你指定的参数吃掉,剩下的就交给编译器从函数参数列表中推导啦。 ``` -### 1.3 整型也可是Template参数 +## 2.4. 整型也可是Template参数 模板参数除了类型外(包括基本类型、结构、类类型等),也可以是一个整型数(Integral Number)。这里的整型数比较宽泛,包括布尔型,不同位数、有无符号的整型,甚至包括指针。我们将整型的模板参数和类型作为模板参数来做一个对比: @@ -608,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),是一个改变语言内涵,拓展语言外延的存在。 程序最根本的目的是什么?复现真实世界或人所构想的规律,减少重复工作的成本,或通过提升规模完成人所不能及之事。但是世间之事万千,有限的程序如何重现复杂的世界呢? @@ -762,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 @@ -798,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) @@ -869,7 +854,7 @@ Variant result = addFloatOrMulInt(aVar, bVar); 在模板代码中,这个"合适的机制"就是指"特化"和"部分特化(Partial Specialization)",后者也叫"偏特化"。 -#### 2.2.2 特化 +### 3.2.2. 特化 我的高中物理老师对我说过一句令我受用至今的话:把自己能做的事情做好。编写模板程序也是一样。当你试图用模板解决问题之前,先撇开那些复杂的语法要素,用最直观的方式表达你的需求: @@ -1088,7 +1073,7 @@ void PrintID() 如果你体味到了这一点,那么恭喜你,你的模板元编程已经开悟了。 -#### 2.2.3 特化:一些其它问题 +### 3.2.3. 特化:一些其它问题 在上一节结束之后,你一定做了许多的练习。我们再来做三个练习。第一,给`float`一个ID;第二,给`void*`一个ID;第三,给任意类型的指针一个ID。先来做第一个: @@ -1166,7 +1151,7 @@ void PrintID() { cout << "ID of float: " << TypeToID::ID << endl; // Print "1" cout << "NotID of float: " << TypeToID::NotID << endl; // Error! TypeToID使用的特化的类,这个类的实现没有NotID这个成员。 - cout << "ID of double: " << TypeToID::ID << endl; // Error! TypeToID是由模板类实例化出来的,它只有NotID,没有ID这个成员。 + cout << "ID of double: " << TypeToID::ID << endl; // Error! TypeToID是由类模板实例化出来的,它只有NotID,没有ID这个成员。 } ``` @@ -1353,9 +1338,9 @@ void PrintID() 但是呢,直觉对付更加复杂的问题还是没用的(也不是没用,主要是你没这个直觉了)。我们要把这个直觉,转换成合理的规则——即模板的匹配规则。 当然,这个匹配规则是对复杂问题用的,所以我们会到实在一眼看不出来的时候才会动用它。一开始我们只要把握:**模板是从最特殊到最一般形式进行匹配的** 就可以了。 -### 2.3 即用即推导 +## 3.3. 即用即推导 -#### 2.3.1 视若无睹的语法错误 +### 3.3.1. 视若无睹的语法错误 这一节我们将讲述模板一个非常重要的行为特点:那就是什么时候编译器会对模板进行推导,推导到什么程度。 这一知识,对于理解模板的编译期行为、以及修正模板编译错误都非常重要。 @@ -1391,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代码: @@ -1506,7 +1491,7 @@ template struct X { 接下来,我们就来解决2.3.1节中留下的几个问题。 -先看第四个问题。为什么MSVC中,模板函数的定义内不管填什么编译器都不报错?因为MSVC在分析模板中成员函数定义时没有做任何事情。至于为啥连"大王叫我来巡山"都能过得去,这是C++语法/语义分析的特殊性导致的。 +先看第四个问题。为什么MSVC中,函数模板的定义内不管填什么编译器都不报错?因为MSVC在分析模板中成员函数定义时没有做任何事情。至于为啥连"大王叫我来巡山"都能过得去,这是C++语法/语义分析的特殊性导致的。 C++是个非常复杂的语言,以至于它的编译器,不可能通过词法-语法-语义多趟分析清晰分割,因为它的语义将会直接干扰到语法: ```C++ @@ -1626,7 +1611,7 @@ error: variable has incomplete type 'A' 1 error generated. ``` -符合标准的写法需要将模板类的定义,和模板函数的定义分离开: +符合标准的写法需要将类模板的定义,和函数模板的定义分离开: > TODO 此处例子不够恰当,并且描述有歧义。需要在未来版本中修订。 @@ -1649,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 中提到的四个问题,还有三个没有解决: @@ -1744,7 +1729,7 @@ template struct X { }; ``` -### 2.4 本章小结 +## 3.4. 本章小结 这一章是写作中最艰难的一章,中间停滞了将近一年。因为要说清楚C++模板中一些语法噪音和设计决议并不是一件轻松的事情。不过通过这一章的学习,我们知道了下面这几件事情: @@ -1756,11 +1741,11 @@ template struct X { 从下一章开始,我们将进入元编程环节。我们将使用大量的示例,一方面帮助巩固大家学到的模板知识,一方面也会引导大家使用函数式思维去解决常见的问题。 -## 3 深入理解特化与偏特化 +# 4. 深入理解特化与偏特化 -### 3.1 正确的理解偏特化 +## 4.1. 正确的理解偏特化 -#### 3.1.1 偏特化与函数重载的比较 +### 4.1.1. 偏特化与函数重载的比较 在前面的章节中,我们介绍了偏特化的形式、也介绍了简单的用例。因为偏特化和函数重载存在着形式上的相似性,因此初学者便会借用重载的概念,来理解偏特化的行为。只是,重载和偏特化尽管相似但仍有差异。 @@ -1914,7 +1899,7 @@ X v8; 其他的示例可以先自己推测一下, 再去编译器上尝试一番:[`goo.gl/9UVzje`](http://goo.gl/9UVzje)。 -#### 3.1.2 不定长的模板参数 +### 4.1.2. 不定长的模板参数 不过这个时候也许你还不死心。有没有一种办法能够让例子`DoWork`像重载一样,支持对长度不一的参数列表分别偏特化/特化呢? @@ -2033,7 +2018,7 @@ template class Y {}; // (4) error! 在这里,我们只提到了变长模板参数的声明,如何使用我们将在第四章讲述。 -#### 3.1.3 模板的默认实参 +### 4.1.3. 模板的默认实参 在上一节中,我们介绍了模板对默认实参的支持。当时我们的例子很简单,默认模板实参是一个确定的类型`void`或者自定义的`null_type`: @@ -2050,7 +2035,7 @@ template < 第一步,我们先把浮点正确的写出来: ```C++ -#include +include template T CustomDiv(T lhs, T rhs) { // Custom Div的实现 @@ -2085,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; @@ -2126,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; @@ -2180,7 +2165,7 @@ void foo(){ * A和B都与模板实参无法匹配,所以使用原型,调用`CustomDiv` -### 3.2 后悔药:SFINAE +## 4.2. 后悔药:SFINAE 考虑下面这个函数模板: @@ -2365,7 +2350,9 @@ foo( int // 这里都不需要 substitution ) { - // 整个实现部分,都没有 substitution。这个很关键。 + // 根据定义,substitution只发生在函数签名上。 + // 故而整个函数实现部分都不会存在 substitution。 + // 这是一个重点需要记住。 } ``` @@ -2476,11 +2463,11 @@ void inc_counter(ICounter& counterObj); 嗯,你说的没错,在这里这个特性一点都没用。 -这也提醒我们,当你觉得需要写 `enable_if` 的时候,首先要考虑到以下可能性: +这也提醒我们,当你觉得需要写 `enable_if` 的时候,首先要考虑到以下可能的替代方案: - * 重载(对模板函数) + * 重载(适用于函数模板) - * 偏特化(对模板类而言) + * 偏特化(适用于类模板) * 虚函数 @@ -2524,9 +2511,9 @@ void doSomething() { ```C++ -#include -#include -#include +include +include +include struct ICounter {}; struct Counter: public ICounter { @@ -2631,41 +2618,147 @@ void foo( 虽然它写起来并不直观,但是对于既没有编译器自省、也没有Concept的C++11来说,已经是最好的选择了。 -(补充例子:构造函数上的enable_if) +## 4.3. Concept "概念":对模板参数约束的直接描述 + +### 4.3.1. "概念" 解决了什么问题 +从上一节可以看出,我们兜兜转转了那么久,就是为了解决两个问题: + +1. 在模板进行特化的时候,盘算一下并告诉编译器这里能不能特化; + +2. 在函数决议面临多个候选的时候,如果有且仅有其中一个原型能够被函数决议接纳,那就决定是你了! + +如果语言能允许用户直接描述需求并传达给编译器,不就不用这么麻烦了么。其实在很多现代语言中,都有类似的语言要素存在,比如C的约束(constraint on type parameters): + +``` C +public class Employee { + // ... +} + +public class GenericList where T : Employee { + // ... +} +``` +上例就非常清晰的呈现了我们对`GenericList`中`T`的要求是:它得是一个`Employee`或`Employee`的子类。 + +这种"清晰的"类型约束,在C++中称作概念(Concept)。最早有迹可循的概念相关工作应当从2003年后就开始了。2006年Bjarne在POPL 06上的一篇报告"Specifying C++ concepts"算是"近代"Concept工作的首次公开亮相。委员会为Concept筹划数年,在2008年提出了第一版Concepts提案,试图进入C++0x的标准中。这也是Concept第一次在C++社群当中被广泛"炒作"。不过2009年的会议,让"近代"Concept在N2617草案戛然而止。 + +2013年之后,Concept改头换面为Concept Lite提案(N3701)卷土重来,历经多方博弈和多轮演化,最终形成了我们在C++20里看到的Concept。有关于Concept的方法论和比较,B.S. 在白皮书中有过比较详细的交代。 + +总之,在concept进入标准之后,模板特化的类型约束写起来就方便与直接多了。而且这些约束之间还可以像表达式一样复用和组合。虽然因为C++类型系统自身的琐碎导致基础库中的concept仍然相当的冗长,但是比起之前起码具备了可用性。 + +比如我们拿上一节中最后一个例子作为对比: +``` C++ +// SFINAE +template +void foo( + ArgT&& a, + typename std::enabled_if< + std::is_same, float>::value +>::type* = nullptr +); +// Concept +template + requires std::same_as, float> +void foo(ArgT&& a) { +} +``` +可以看到,concept之后的表达式消除了语法噪音,显得更为简洁一些。而对于之前++的例子,concept下则更为扼要: +```C++ +template concept Incrementable = requires (T t) { ++t; } +template +void inc_counter(T& intTypeCounter) { + ++intTypeCounter; +} +``` +直接告诉编译器,我们对T的要求是你得有`++`。 + +当然有人会问,那能不能直接写成以下形式,不是更简单吗? + +``` C++ +template requires (T t) { ++t; } +void inc_counter(T& cnt); +``` + +答案是:不能。 +因为`requires`作为关键字/保留字是存在二义性的。当它用于函数模板或者类模板的声明时,它是一个constraint,后面需要跟着concept表达式;而用于concept中,则是一个required expression,用于concept的求解。既然constraint后面跟着一个concept表达式,而requires也可以用来定义一个concept expression,那么一个风骚的想法形成了:我能不能用 `requires (requires (T t) {++t;})` 来约束模板参数呢? + +当然是可以的!C++就是这么的简(~~有~~)单(~~病~~)! + +``` C++ +template requires (requires (T t) { ++t; }) +void inc_counter(T& cnt); +``` + +总而言之,除了这些烦人的问题,"概念"的出现,使得模板的出错提示也清爽了些许 —— 虽然大佬们都在鼓吹concept让模板出错多么好调试,但是实际上模板出错,有一半是来源自类型系统本质上的复杂性,概念并不能解决这一问题。 + +比如这里使用SFINAE的提示: + +``` +:23:5: error: no matching function for call to 'Inc' + Inc(y); + ^~~ +:5:6: note: candidate template ignored: substitution failure [with T = X]: cannot increment value of type 'X' +void Inc(T& v, std::decay_t* = nullptr) + ^ ~~ +``` + +而这里是使用了concept的提示。 +``` +:25:5: error: no matching function for call to 'Inc_Concept' + Inc_Concept(y); + ^~~~~~~~~~~ +:13:6: note: candidate template ignored: constraints not satisfied [with T = X] +void Inc_Concept(T& v) + ^ +:12:11: note: because 'X' does not satisfy 'Incrementable' +template + ^ +:10:41: note: because '++t' would be invalid: cannot increment value of type 'X' +concept Incrementable = requires(T t) { ++t; }; +``` + +虽然在这个例子中,通过 *Concept* 获得出错提示看起来要比使用 *SFINAE* 所获得的错误描述要更长一点,但是对于更加复杂类型来说,则会友善许多。以后会找个例子给大家陈述。 -## !!! 以下章节未完成 !!! +### 4.3.2. "概念"入门 -## 4 元编程下的数据结构与算法 -### 4.1 表达式与数值计算 -### 4.1 获得类型的属性——类型萃取(Type Traits) -### 4.2 列表与数组 -### 4.3 字典结构 -### 4.4 "快速"排序 -### 4.5 其它常用的"轮子" -## 5 模板的进阶技巧 -### 5.1 嵌入类 -### 5.2 Template-Template Class -### 5.3 高阶函数 -### 5.4 闭包:模板的"基于对象" +# 5. 未完成章节 + +``` +# 6. 元编程下的数据结构与算法 +## 6.1. 表达式与数值计算 +## 6.2. 获得类型的属性——类型萃取(Type Traits) +## 6.3. 列表与数组 +## 6.4. 字典结构 +## 6.5. "快速"排序 +## 6.6. 其它常用的"轮子" + +# 7. 非模板的编译期计算 + +# 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

AltStyle によって変換されたページ (->オリジナル) /