├── .gitignore
├── C++-Templates-The-Complete-Guide.tex
├── LICENSE
├── README.md
├── content
├── 1
│ ├── Section.tex
│ ├── chapter1
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ └── 7.tex
│ ├── chapter10
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ └── 6.tex
│ ├── chapter11
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ └── 7.tex
│ ├── chapter2
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 10.tex
│ │ ├── 11.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ ├── 8.tex
│ │ └── 9.tex
│ ├── chapter3
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ └── 5.tex
│ ├── chapter4
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ └── 5.tex
│ ├── chapter5
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ └── 8.tex
│ ├── chapter6
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ └── 6.tex
│ ├── chapter7
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ └── 7.tex
│ ├── chapter8
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ └── 6.tex
│ └── chapter9
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ └── 6.tex
├── 2
│ ├── Section.tex
│ ├── chapter12
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ └── 6.tex
│ ├── chapter13
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ └── images
│ │ │ └── 1.png
│ ├── chapter14
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ └── 8.tex
│ ├── chapter15
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 10.tex
│ │ ├── 11.tex
│ │ ├── 12.tex
│ │ ├── 13.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ ├── 8.tex
│ │ ├── 9.tex
│ │ └── images
│ │ │ └── 1.png
│ ├── chapter16
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ └── 6.tex
│ └── chapter17
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 10.tex
│ │ ├── 11.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ ├── 8.tex
│ │ └── 9.tex
├── 3
│ ├── Section.tex
│ ├── chapter18
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ └── images
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ ├── 3.png
│ │ │ ├── 4.png
│ │ │ └── 5.png
│ ├── chapter19
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 10.tex
│ │ ├── 11.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ ├── 8.tex
│ │ └── 9.tex
│ ├── chapter20
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ └── 7.tex
│ ├── chapter21
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ └── images
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ ├── 3.png
│ │ │ └── 4.png
│ ├── chapter22
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ └── 7.tex
│ ├── chapter23
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ └── 7.tex
│ ├── chapter24
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ └── 6.tex
│ ├── chapter25
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ └── 7.tex
│ ├── chapter26
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ └── 7.tex
│ ├── chapter27
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ └── images
│ │ │ └── 1.png
│ └── chapter28
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ └── 6.tex
├── About-This-Book.tex
├── Acknowledgments-for-the-First.tex
├── Acknowledgments-for-the-Second.tex
├── Appendix
│ ├── A
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ └── 3.tex
│ ├── B
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ └── images
│ │ │ └── 1.png
│ ├── C
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ └── 3.tex
│ ├── D
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ ├── 4.tex
│ │ ├── 5.tex
│ │ ├── 6.tex
│ │ ├── 7.tex
│ │ └── images
│ │ │ └── 1.png
│ └── E
│ │ ├── 0.tex
│ │ ├── 1.tex
│ │ ├── 2.tex
│ │ ├── 3.tex
│ │ └── 4.tex
├── Bibliography.tex
├── Glossary.tex
└── preface.tex
└── cover.jpg
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pdf
2 | *.aux
3 | *.log
4 | *.out
5 | *.gz
6 | *toc
7 | *.listing
8 | *.synctex(busy)
9 | /CPP-Templates-2nd---master/
10 | /cpp-templates-2nd-master/
11 | *.zip
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # C++ Templates
2 |
3 | *Second Edition*
4 |
5 |
6 |
7 | * 作者:David Vandevoorde,Nicolai M. Josuttis,Douglas Gregor
8 | * 译者:陈晓伟
9 | * 首次发布时间:2017年9月8日([来源](https://www.amazon.com/C-Templates-Complete-Guide-2nd/dp/0321714121))
10 |
11 | > 翻译是译者用自己的思想,换一种语言,对原作者想法的重新阐释。鉴于我的学识所限,误解和错译在所难免。如果你能买到本书的原版,且有能力阅读英文,请直接去读原文。因为与之相较,我的译文可能根本不值得一读。
12 | >
13 | >
— 云风,程序员修炼之道第2版译者
14 | 15 | PDF可在本库的[Release页面](https://github.com/xiaoweiChen/Cpp-Templates-2nd/releases)获取。 16 | 17 | ## 本书概述 18 | 19 | 模板是C++中一个强大的特性,但对模板的误解,并未随着C++语言和开发社区的发展而消弭,从而无法使模板无法发挥其全力。本书的三位作者,同时作为C++专家,展示了如何使用现代模板来构建干净、快捷、高效、容易维护的软件。 20 | 21 | 第二版对C++11、C++14和C++17标准进行了更新,对改进模板或与模板交互的特性进行了解释,包括可变参数模板、泛型Lambda、类模板参数演绎、编译时if、转发引用和用户定义文字。还深入研究了一些基本的语言概念(比如值类别),并包含了所有标准类型特征。 22 | 23 | 本书从基本概念和相关语言特征开始,其余部分作为参考。先关注语言,再是编码、高级应用程序和复杂的惯用法。过程中,示例清楚地说明了抽象概念,并演示了模板的最佳实践。 24 | 25 | #### 关键特性 26 | 27 | - 准确理解模板的行为,避免陷阱 28 | 29 | - 使用模板编写有效、灵活、可维护的软件 30 | 31 | - 掌握有效的习语和技巧 32 | 33 | - 保持性能或安全的情况下重用源码 34 | 35 | - C++标准库中的泛型编程 36 | 37 | - 预览即将发布的“概念"特性 38 | 39 | 40 | 41 | ## 适读人群 42 | 43 | 如果您是使用C++的开发人员,想要学习或复习模板,请仔细阅读第1部分。即使已经非常熟悉模板,快速浏览这一部分也有助于熟悉本书使用的编程方式和术语。该部分也涵盖了如何组织模板相关代码的内容。 44 | 45 | 可以按自己喜欢方式学习。第2部分中有模板更多的许多细节信息,也可以在第3部分中阅读实用的编码技术(并参考第2部分了解相关的语言问题)。如果阅读这本书是为了应对开发中的具体问题,那么后一种方法可能有助于问题的解决。 46 | 47 | 附录包含了许多在正文中经常提到的信息,我们也试图让其变得更有趣。 48 | 49 | 根据经验,学习新东西的最好方法是看例子。因此,可以在本书中找到大量的例子。有些只是用几行代码解释一个抽象概念,而另一些是具体应用的完整源码。后一种示例将通过注释来说明需要包含程序代码的文件。可以在这本书的网站::value;
18 | };
19 |
20 | template {
22 | static constexpr bool value = (p%2 != 0);
23 | };
24 |
25 | template ::value;
29 | };
30 |
31 | // special cases (to avoid endless recursion with template instantiation):
32 | template<>
33 | struct IsPrime<0> { static constexpr bool value = false; };
34 | template<>
35 | struct IsPrime<1> { static constexpr bool value = false; };
36 | template<>
37 | struct IsPrime<2> { static constexpr bool value = true; };
38 | template<>
39 | struct IsPrime<3> { static constexpr bool value = true; };
40 | \end{lstlisting}
41 |
42 | IsPrime<>模板返回成员值,无论传递的模板参数p是否是一个素数。为了实现,实例化DoIsPrime<>,其递归地展开为一个表达式,检查p/2和2之间的每个除数d是否能整除p。
43 |
44 | 例如,表达式为
45 |
46 | \begin{lstlisting}[style=styleCXX]
47 | IsPrime<9>::value
48 | \end{lstlisting}
49 |
50 | 扩展为
51 |
52 | \begin{lstlisting}[style=styleCXX]
53 | DoIsPrime<9,4>::value
54 | \end{lstlisting}
55 |
56 | 继续扩展
57 |
58 | \begin{lstlisting}[style=styleCXX]
59 | 9%4!=0 && DoIsPrime<9,3>::value
60 | \end{lstlisting}
61 |
62 | 继续扩展
63 |
64 | \begin{lstlisting}[style=styleCXX]
65 | 9%4!=0 && 9%3!=0 && DoIsPrime<9,2>::value
66 | \end{lstlisting}
67 |
68 | 继续扩展
69 |
70 | \begin{lstlisting}[style=styleCXX]
71 | 9%4!=0 && 9%3!=0 && 9%2!=0
72 | \end{lstlisting}
73 |
74 | 计算结果为false,因为9\%3是0。
75 |
76 | 正如这个实例链所示:
77 |
78 | \begin{itemize}
79 | \item
80 | 使用递归展开DoIsPrime<>来遍历从p/2到2的所有除数,以确定这些除数是否能整除给定整数。
81 |
82 | \item
83 | 当d等于2时,DoIsPrime<>的偏特化作为结束递归。
84 | \end{itemize}
85 |
86 | 注意,所有这些都在编译时完成。也就是说,
87 |
88 | \begin{lstlisting}[style=styleCXX]
89 | IsPrime<9>::value
90 | \end{lstlisting}
91 |
92 | 在编译时展开为false。
93 |
94 | 模板语法可以说是笨拙的,但类似的代码自C++98(或更早)就一直有效,并且可以提高运行库的效率。
95 |
96 | \begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
97 | \hspace*{0.75cm}C++11前,通常将值成员声明为枚举数常量,而不是静态数据成员,以避免静态数据成员需要在类外定义(参见第23.6节了解详细信息)。例如:
98 | \begin{lstlisting}[style=styleCXX]
99 | enum f value = (p%d != 0) && DoIsPrime ::value g;
100 | \end{lstlisting}
101 | \end{tcolorbox}
102 |
103 | 详见第23章。
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/content/1/chapter8/2.tex:
--------------------------------------------------------------------------------
1 | C++11引入了一个新特性constexpr,简化了各种形式的编译时计算。特别是,如果有输入适当,可以在编译时对constexpr函数求值。虽然在C++11中引入constexpr函数有严格限制(每个constexpr函数本质上都需要包含一个return语句),但在C++14中,这些限制大部分都取消了。当然,成功地计算constexpr函数仍然需要所有的计算步骤,在编译时是可行和有效的:这排除了堆分配或抛出异常的情况。
2 |
3 | 我们测试一个数字是否是素数的例子,可以使用C++11进行如下实现:
4 |
5 | \hspace*{\fill} \\ %插入空行
6 | \noindent
7 | \textit{basics/isprime11.hpp}
8 | \begin{lstlisting}[style=styleCXX]
9 | constexpr bool
10 | doIsPrime (unsigned p, unsigned d) // p: number to check, d: current divisor
11 | {
12 | return d!=2 ? (p%d!=0) && doIsPrime(p,d-1) // check this and smaller divisors
13 | : (p%2!=0); // end recursion if divisor is 2
14 | }
15 |
16 | constexpr bool isPrime (unsigned p)
17 | {
18 | return p < 4 ? !(p<2) // handle special cases
19 | : doIsPrime(p,p/2); // start recursion with divisor from p/2
20 | }
21 | \end{lstlisting}
22 |
23 | 由于只有一条语句的限制,只能使用条件操作符作为选择机制,并且需要递归来遍历元素。但其语法是普通的C++函数代码,这使得它比依赖于模板实例化的第一个版本更容易使用。
24 |
25 | C++14中,constexpr函数可以使用通用C++代码中的控制结构。因此,不用编写笨拙的模板代码或有些“奇怪的”单行程序,现在只使用普通的for循环:
26 |
27 | \hspace*{\fill} \\ %插入空行
28 | \noindent
29 | \textit{basics/isprime14.hpp}
30 | \begin{lstlisting}[style=styleCXX]
31 | constexpr bool isPrime (unsigned int p)
32 | {
33 | for (unsigned int d=2; d<=p/2; ++d) {
34 | if (p % d == 0) {
35 | return false; // found divisor without remainder
36 | }
37 | }
38 | return p > 1; // no divisor without remainder found
39 | }
40 | \end{lstlisting}
41 |
42 | 使用C++11和C++14版本的constexpr isPrime()实现,可以直接调用
43 |
44 | \begin{lstlisting}[style=styleCXX]
45 | isPrime(9)
46 | \end{lstlisting}
47 |
48 | 找出9是否为质数。可以在编译时这样做,但不一定要这样做。在需要编译时值的上下文中(例如,数组长度或非类型模板参数),编译器将尝试在编译时计算对constexpr函数的调用。若无法计算,则会产生错误(因为最后必须生成一个常量)。其他上下文中,编译器在编译时可能尝试或不尝试求值,但若这样的求值失败,是不会产生错误信息,而是将问题留给运行时。
49 |
50 | \begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
51 | \hspace*{0.75cm}2017年写这本书的时候,编译器似乎确实在尝试编译时求值,即使不严格的情况下。
52 | \end{tcolorbox}
53 |
54 | 例如:
55 |
56 | \begin{lstlisting}[style=styleCXX]
57 | constexpr bool b1 = isPrime(9); // evaluated at compile time
58 | \end{lstlisting}
59 |
60 | 编译时计算该值,也适用于
61 |
62 | \begin{lstlisting}[style=styleCXX]
63 | const bool b2 = isPrime(9); // evaluated at compile time if in namespace scope
64 | \end{lstlisting}
65 |
66 | 假设b2是全局定义的或在命名空间中定义的。块作用域中,编译器可以决定是在编译时计算,还是在运行时计算。
67 |
68 | \begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
69 | \hspace*{0.75cm}理论上,即使使用了constexpr,编译器也可以决定在运行时计算b的初始值。编译器只需要在编译时检查它是否可计算即可。
70 | \end{tcolorbox}
71 |
72 | 例如,这样:
73 |
74 | \begin{lstlisting}[style=styleCXX]
75 | bool fiftySevenIsPrime() {
76 | return isPrime(57); // evaluated at compile or running time
77 | }
78 | \end{lstlisting}
79 |
80 | 编译器可能会在编译时计算对isPrime的调用。
81 |
82 | 另外:
83 |
84 | \begin{lstlisting}[style=styleCXX]
85 | int x;
86 | ...
87 | std::cout << isPrime(x); // evaluated at run time
88 | \end{lstlisting}
89 |
90 | 将生成在运行时计算x是否为素数的代码。
91 |
92 |
--------------------------------------------------------------------------------
/content/1/chapter8/3.tex:
--------------------------------------------------------------------------------
1 | isPrime()等编译时测试的一种应用是,在编译时使用偏特化在不同实现之间进行选择。
2 |
3 | 例如,可以根据模板参数是否是素数来选择不同的实现:
4 |
5 | \begin{lstlisting}[style=styleCXX]
6 | // primary helper template:
7 | template
14 | ...
15 | \end{lstlisting}
16 |
17 | 和
18 |
19 | \begin{lstlisting}[style=styleCXX]
20 | #include
21 | #include
39 | ...
40 | \end{lstlisting}
41 |
42 | 可以对这个文件进行预编译,然后每个使用标准库的程序文件就可以按如下方式启动:
43 |
44 | \begin{lstlisting}[style=styleCXX]
45 | #include "std.hpp"
46 | ...
47 | \end{lstlisting}
48 |
49 | 通常,这将需要一段时间来编译。但若系统有足够的内存,预编译头文件的处理速度快于不进行预编译的标准头文件。标准头文件在这种方式下特别方便,因为它们很少更改,因此std.hpp文件的预编译头文件只需要构建一次。否则,预编译头文件通常是项目依赖配置的一部分(例如,会根据需要由构建工具或集成开发环境(IDE)的项目构建工具进行更新)。
50 |
51 | 管理预编译头文件的方法是创建预编译头文件层,这些头文件层从最广泛使用和最稳定的头文件(例如,std.hpp头文件)到那些不会随时更改的头文件,因此仍然可以使用预编译的头文件。但若头文件需要大量开发,那创建预编译的头文件所花费的时间,可能比重用它们要要多。这种方法的关键概念是,可以重用较稳定层的预编译头,以提高较不稳定头文件的预编译时间。例如,假设除了std.hpp头文件(已经预编译过了),还要定义了一个core.hpp头文件,包含了一些特定于项目的附加工具,但仍然具有一定程度的稳定性:
52 |
53 | \begin{lstlisting}[style=styleCXX]
54 | #include "std.hpp"
55 | #include "core_data.hpp"
56 | #include "core_algos.hpp"
57 | ...
58 | \end{lstlisting}
59 |
60 | 因为该文件以\#include "standard.hpp"开始,编译器可以加载相关的预编译头文件,并继续下一行,而无需重新编译所有标准头文件。当文件完全处理后,可以生成一个新的预编译头文件。因为编译器可以加载后一个预编译头文件,多以应用可以使用\#include "core.hpp"来快速提供对大量功能的访问。
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/content/1/chapter9/5.tex:
--------------------------------------------------------------------------------
1 | 在头文件和CPP文件中组织源代码具有一定义规则或以ODR形式的结果。附录A对此规则进行了讨论。
2 |
3 | 包含模型是一个实用的方式,主要由C++编译器的现有实践决定。第一个C++实现很特殊:包含的模板定义是隐式的,从而造成了某种分离的错觉(参见第14章了解这个原始模型的详细信息)。
4 |
5 | 第一个C++标准([C++98])通过导出的模板对模板编译的分离模型提供了支持。分离模型允许标记导出的模板声明在头文件中声明,而它们相应的定义放在CPP文件中,非常像非模板代码的声明和定义。与包含模型不同的是,这个模型不基于现有实现的理论模型,而且实现本身比C++标准化委员预期的要复杂得多。它花了五年多的时间才发布了第一个实现(2002年5月),此后的几年里没有出现其他实现。为了更好地使C++标准与现有的实践保持一致,标准化委员会在C++11中删除了导出模板。有兴趣了解更多分离模型细节(和陷阱)的读者可以阅读本书第一版的6.3和10.3节([VandevoordeJosuttisTemplates1st])。
6 |
7 | 有时很容易想象扩展预编译头概念的方法,以便在编译中加载多个头文件,这允许使用更细粒度的方法进行预编译。这里的障碍主要是预处理器:头文件中的宏可能会改变后续头文件的含义。然而,当文件预编译,宏处理就完成了,而试图为其他头文件引入的预处理器效果修订预编译头文件不现实。不久的将来,会有一个称为“模块”的新语言特性(参见17.11节)添加到C++中,来解决这个问题(宏定义不能泄露到模块接口中)。
--------------------------------------------------------------------------------
/content/1/chapter9/6.tex:
--------------------------------------------------------------------------------
1 |
2 | \begin{itemize}
3 | \item
4 | 模板的包含模型是组织模板代码广泛使用的方式。备选方案将在第14章中讨论。
5 |
6 | \item
7 | 只有在类或结构外部的头文件中定义函数模板的特化时才需要内联。
8 |
9 | \item
10 | 要利用预编译的头文件,请确保对\#include保持相同的顺序。
11 |
12 | \item
13 | 调试使用模板的代码很有挑战性。
14 | \end{itemize}
--------------------------------------------------------------------------------
/content/2/Section.tex:
--------------------------------------------------------------------------------
1 | 本书的第一部分提供了C++模板底层的大多数语言概念,这些表述足以回答日常C++编程中可能出现的大多数问题。本书的第二部分提供了参考,回答了将语应用于高级软件时出现的问题。可以在第一次阅读时跳过这一部分,然后根据后面章节中的引用或在索引中查找某个概念后返回到特定的章节。
2 |
3 | 我们的目标是清晰且完整,但也保持讨论的简洁。所以例子很简短,而且常常比较有代表性。这也确保了我们的讨论不会偏离主题,从而避免涉及到不相关的话题。
4 |
5 | 此外,我们还将探讨C++中模板语言特性未来可能的更改和扩展。
6 |
7 | 这部分的主题包括:
8 |
9 | \begin{itemize}
10 | \item
11 | 基础的模板声明问题
12 |
13 | \item
14 | 模板中名称的含义
15 |
16 | \item
17 | C++模板实例化机制
18 |
19 | \item
20 | 模板参数的推导规则
21 |
22 | \item
23 | 特化和重载
24 |
25 | \item
26 | 未来的可能性
27 | \end{itemize}
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/content/2/chapter12/0.tex:
--------------------------------------------------------------------------------
1 | 这章中,将深入了解本书第一部分中介绍的基础知识:模板声明、模板形参和实参的限制等。
--------------------------------------------------------------------------------
/content/2/chapter12/6.tex:
--------------------------------------------------------------------------------
1 | 自20世纪80年代后期,C++模板的一般概念和语法保持相对稳定。类模板和函数模板是初始模板工具的一部分,类型参数和非类型参数也是。
2 |
3 | 然而,最初的设计中也有一些重要功能的添加,主要是出于C++标准库的需要。成员模板可能是这些新增功能中最基本的。奇怪的是,只有成员函数模板正式纳入了C++标准。由于编辑上的疏忽,成员类模板成为标准的一部分。
4 |
5 | 友元模板、默认模板参数和双重模板参数,是在C++98标准化过程中出现的。声明双重模板参数的能力有时称为高阶泛型。最初引入的原因是为了支持C++标准库中的某个分配器模型,但这个分配器模型后来使用了不依赖于双重模板参数的模型。后来,双重模板参数几乎要从该语言中删除了,直到1998年标准的标准化过程的时候,规范仍然是不完整。最终,委员会的大多数成员决定保留它们,从而形成了新的标准。
6 |
7 | 别名模板是2011年标准的一部分。别名模板与经常要求的“类型定义模板”特性具有相同的需求,它使编写一个模板变得容易,而这个模板只不过是现有类模板的不同拼写。使之成为标准的规范(N2258)是由Gabriel Dos Reis和Bjarne Stroustrup编写的,Mat Marcus也参与了该提案的早期草案。Gaby还为C++14(N3651)制定了变量模板建议的细节。最初,该提案只打算支持constexpr变量,但在标准草案中采用时,这一限制已经取消了。
8 |
9 | 可变参数模板是由C++11标准库和Boost库(参见[Boost])的需求驱动的,其中C++模板库使用越来越高级(和复杂)的技术可以接受任意数量模板参数。Doug Gregor, J{\"a}kko Jarvi, Gary Powell, Jens Maurer和Jason Merrill提供了标准(N2242)的初始规范。开发规范的同时,Doug还开发了该特性的原始实现(在GNU的GCC中),这对在标准库中使用该特性有很大帮助。
10 |
11 | 折叠表达式是Andrew Sutton和Richard Smith的成果,通过N4191将其添加到C++17中。
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/content/2/chapter13/0.tex:
--------------------------------------------------------------------------------
1 | 大多数编程语言中,名称是一个基本概念。它们是开发者可以引用之前构造的实体的方法。C++编译器遇到一个名称时,必须“查找”以识别所引用的实体。从实现者的角度来看,C++在这方面是一门很难的语言。C++语句x*y;,如果x和y是变量名,这条语句就是一个乘法语句,但如果x是类型名,那么这条语句将y声明为指向类型为x的实体的指针。
2 |
3 | 这个小示例表明C++(像C一样)是一种上下文敏感的语言:若不了解上下文,就不能总是理解一个构造。这与模板有什么关系?模板构造必须处理多个上下文:(1)模板出现的上下文,(2)模板实例化的上下文,(3)模板实例化的模板参数相关的上下文。因此,在C++中必须非常小心地处理“名称”。
--------------------------------------------------------------------------------
/content/2/chapter13/5.tex:
--------------------------------------------------------------------------------
1 | 真正用于解析模板定义的第一个编译器是在20世纪90年代中期,由一家名为Taligent的公司开发的。在此之前——甚至几年后——大多数编译器都将模板视为在实例化时才处理的标记。因此,除了找到模板定义结束位置等少许操作以外,没有进行任何解析。撰写本文时,Microsoft Visual C++编译器仍然以这种方式工作。爱迪生设计集团(EDG)的编译器前端使用了一种混合技术,模板在内部视为一个带注释的标记序列,在需要的模式中执行“通用解析”来验证语法(EDG的产品模拟多个其他编译器,可以很好地模拟微软编译器的行为)。
2 |
3 | Bill Gibbons是Taligent在C++委员会的代表,其极力主张让模板可以无二义性地进行解析。然而,直到惠普公司完成第一个完整的编译器之后,Taligent公司的努力才真正产品化,也才有了一个真正编译模板的C++编译器。和其他具有竞争性优点的产品一样,这个C++编译器很快就由于高质量的错误信息而得到业界的认可。模板的错误信息不会总延迟到实例化时才发出,也要归功于这个编译器。
4 |
5 | 模板的早期开发过程中,Tom Pennello(Metaware公司的一位著名解析专家)就意识到了尖括号所带来的一些问题。Stroustrup也对这个话题进行了讨论[StroustrupDnE],而且认为人们更喜欢阅读尖括号,而不是圆括号。然而,除了尖括号和圆括号,还存在其他的一些可能性:Pennello在1991年的C++标准大会(在达拉斯举办)上特别地提议使用大括号,例如(List{::X})。
6 |
7 | \begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
8 | \hspace*{0.75cm}使用括号也不是完全没有问题。具体来说,特化类模板的语法需要进行重大的调整。
9 | \end{tcolorbox}
10 |
11 | 那时,因为嵌入在其他模板内部的模板(也称为成员模板)还是不合法的,所以问题的影响范围也非常有限,也就不会涉及到13.3.3节的问题。最后,委员会拒绝了这个取代尖括号的提议。
12 |
13 | 13.4.2节中描述的非依赖型名称和依赖型基类的名称查找规则,是在1993年C++标准中引入的。在1994年,Bjarne Stroustrup的[StroustrupDnE]首次公开描述了这一规则。然而直到1997年惠普才把这一规则引入C++编译器,自那以后出现了大量的派生自依赖型基类的类模板代码。当惠普工程师开始测试该实现时,发现大部分以特殊方式使用模板的代码都无法编译成功了。
14 |
15 | \begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
16 | \hspace*{0.75cm}幸运的是,在发布新功能之前就发现了。
17 | \end{tcolorbox}
18 |
19 | 特别地,STL的所有实现都打破了这一规则。
20 |
21 | \begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
22 | \hspace*{0.75cm}具有讽刺意味的是,第一个实现也是由惠普开发的。
23 | \end{tcolorbox}
24 |
25 | 考虑到客户的转换代码的成本,对于那些“假定非依赖型名称可以在依赖型基类中进行查找”的代码,惠普弱化了相关的错误信息。例如,对于位于类模板作用域的非依赖型名称,若利用标准原则不能找到该名称,C++就会在依赖型基类中进行查找。若仍然找不到,才会给出一个错误而编译失败。然而,若在依赖型基类中找到了该名称,那么就会给出一个警告,对该名称进行标记,并且当成是依赖型名称,然后在实例化时再次查找。
26 |
27 | 查找过程中,“非依赖型基类中的名称,会隐藏相同名称的模板参数(13.4.1节)”这一规则显然是一个疏忽,但修改这一规则的建议还没被C++标准委员会所认可。最好的办法就是避免使用非依赖型基类中的名称作为模板参数名称。命名转换对这一类问题都是一种良好的解决方式。
28 |
29 | 友元注入一度认为是有害的,会使得程序的合法性与实例出现的顺序紧密相关。Bill Gibbons(此时他还在Taligent公司开发编译器)就是解决这一问题的最大支持者,因为消除实例顺序依赖性激活了一个新的、有趣的C++开发领域(传闻Taligent正在做)。然而,Barton-Nackman技巧(21.2.1节)需要友元注入的形式,正是这种特殊的技术使它以基于ADL的(弱化)形式保留在语言中。
30 |
31 | Andrew Koenig首次为操作符函数提出了ADL查找(这就是为什么有时候ADL也称为Koenig查找),动机主要是考虑美观性:“用外围命名空间显式地限定操作符名称”看起来很拖沓(例如,对于a+b,需要这样编写:N::operator+(a,b)),而为每个操作符使用using声明又会让代码看起来非常笨重。因此,才决定操作符可以在参数关联的命名空间中查找。ADL随后扩展到普通函数名称的查找,得以容纳有限种类的友元名称注入,并为模板及其实例支持两阶段查找模型(第14章)。泛化的ADL规则也称作扩展Koenig查找。
32 |
33 | David Vandevoorde通过他的论文N1757在C++11中添加了“尖括号黑客”规范。还通过核心问题1104的解决方案添加了“有向图黑客”,以解决美国对C++11标准草案的审查要求。
--------------------------------------------------------------------------------
/content/2/chapter13/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaoweiChen/Cpp-Templates-2nd/579a1728a33ba4e1f410354a4a427e1668f37190/content/2/chapter13/images/1.png
--------------------------------------------------------------------------------
/content/2/chapter14/0.tex:
--------------------------------------------------------------------------------
1 | 模板实例化是从泛型模板定义中生成类型、函数和变量的过程。
2 |
3 | \begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
4 | \hspace*{0.75cm}术语实例化有时也用来指从类型创建对象。在本书中,用来表示模板实例化。
5 | \end{tcolorbox}
6 |
7 | C++模板实例化的概念非常基础,但有时又错综复杂。因为,模板生成的实体定义不再局限于源代码的位置。模板本身的位置、模板使用的位置,以及模板参数类型定义的位置均在实体的含义中扮演着重要角色。
8 |
9 | 本章中,将解释如何组织源代码以正确使用模板。此外,我们调研了主流C++编译器用于处理模板实例化的各种方法。尽管这些方法在语义上应该等价,但理解编译器实例化策略的基本原则是大有裨益的。构建实际的软件时,每种机制都带有一组小怪癖,并且每种机制都影响了标准C++规范。
--------------------------------------------------------------------------------
/content/2/chapter14/1.tex:
--------------------------------------------------------------------------------
1 | 当C++编译器遇到模板特化时,将通过替换模板参数来创建该特化。
2 |
3 | \begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
4 | \hspace*{0.75cm}术语特化用于一般意义上的实体,是模板的一个特定实例(参见第10章),不涉及第16章中描述的显式特化机制。
5 | \end{tcolorbox}
6 |
7 | 特化是自动完成的,不需要特殊代码(或者模板定义)的指导。这种按需变化的实例化特性将C++模板与其他早期编译语言不同(如Ada或Eiffel;一些语言需要显式的实例化指令,而其他语言则使用运行时分发机制来避免实例化)。这有时也称为隐式或自动实例化。
8 |
9 | 按需实例化要求编译器在使用时,需要经常访问模板及其部分成员的完整定义(不仅仅是声明)。看看下面的示例:
10 |
11 | \begin{lstlisting}[style=styleCXX]
12 | template