Преглед изворни кода

Reclarified 术语 模板类 和 类模板 的正确使用

Ye Wu пре 2 година
родитељ
комит
cd4232437e
1 измењених фајлова са 25 додато и 25 уклоњено
  1. 25 25
      ReadMe.md

+ 25 - 25
ReadMe.md

@@ -17,10 +17,10 @@
     - [2.2.1. “模板类”还是“类模板”](#221-模板类还是类模板)
     - [2.2.2. Class Template的与成员变量定义](#222-class-template的与成员变量定义)
     - [2.2.3. 模板的使用](#223-模板的使用)
-    - [2.2.4. 模板的成员函数定义](#224-模板的成员函数定义)
+    - [2.2.4. 模板的成员函数定义](#224-模板的成员函数定义)
   - [2.3. 函数模板 (Function Tempalte) 入门](#23-函数模板-function-tempalte-入门)
-    - [2.3.1. Function Template 的声明和定义](#231-function-template-的声明和定义)
-    - [2.3.2. 模板函数的使用](#232-模板函数的使用)
+    - [2.3.1. 函数模板的声明和定义](#231-函数模板的声明和定义)
+    - [2.3.2. 函数模板的使用](#232-函数模板的使用)
   - [2.4. 整型也可是Template参数](#24-整型也可是template参数)
   - [2.5. 模板形式与功能是统一的](#25-模板形式与功能是统一的)
 - [3. 模板元编程基础](#3-模板元编程基础)
@@ -73,7 +73,7 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言,
 * 使用过STL;
 * 熟悉一些常用的算法,以及递归等程序设计方法。
 
-此外,尽管第一章会介绍一些Template的基本语法,但是还是会略显单薄。因此也希望读者能对C++ Template最基本语法形式有所了解和掌握;如果会编写基本的模板函数模板类那就更好了。
+此外,尽管第一章会介绍一些Template的基本语法,但是还是会略显单薄。因此也希望读者能对C++ Template最基本语法形式有所了解和掌握;如果会编写基本的函数模板模板那就更好了。
 
 诚如上节所述,本文并不是《C++ Templates》的简单重复,与《Modern C++ Design》交叠更少。从知识结构上,我建议大家可以先读本文,再阅读《C++ Templates》获取更丰富的语法与实现细节,以更进一步;《Modern C++ Design》除了元编程之外,还有很多的泛型编程示例,原则上泛型编程的部分与我所述的内容交叉不大,读者在读完1-3章了解模板的基本规则之后便可阅读《MCD》的相应章节;元编程部分(如Typelist)建议在阅读完本文之后再行阅读,或许会更易理解。
 
@@ -166,7 +166,7 @@ void foo(int a);
 
 在定义完模板参数之后,便可以定义你所需要的类。不过在定义类的时候,除了一般类可以使用的类型外,你还可以使用在模板参数中使用的类型 `T`。可以说,这个 `T`是模板的精髓,因为你可以通过指定模板实参,将T替换成你所需要的类型。
 
-例如我们用`ClassA<int>`来实例化模板ClassA,那么`ClassA<int>`可以等同于以下的定义:
+例如我们用`ClassA<int>`来实例化模板ClassA,那么`ClassA<int>`可以等同于以下的定义:
 
 ``` C++
 // 注意:这并不是有效的C++语法,只是为了说明模板的作用
@@ -175,11 +175,11 @@ typedef class {
 } ClassA<int>;
 ```
 
-可以看出,通过模板参数替换类型,可以获得很多形式相同的新类型,有效减少了代码量。这种用法,我们称之为“泛型”(Generic Programming),它最常见的应用,即是STL中的容器模板
+可以看出,通过模板参数替换类型,可以获得很多形式相同的新类型,有效减少了代码量。这种用法,我们称之为“泛型”(Generic Programming),它最常见的应用,即是STL中的容器模板。
 
 ### 2.2.3. 模板的使用
 
-对于C++来说,类型最重要的作用之一就是用它去产生一个变量。例如我们定义了一个动态数组(列表)的模板`vector`,它对于任意的元素类型都具有push_back和clear的操作,我们便可以如下定义这个类:
+对于C++来说,类型最重要的作用之一就是用它去产生一个变量。例如我们定义了一个动态数组(列表)的模板`vector`,它对于任意的元素类型都具有push_back和clear的操作,我们便可以如下定义这个类:
 
 ```C++
 template <typename T>
@@ -208,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,...] >
@@ -237,9 +237,9 @@ ClassB<int, float>
 当然,在实例化过程中,被绑定到模板参数上的类型(即模板实参)需要与模板形参正确匹配。
 就如同函数一样,如果没有提供足够并匹配的参数,模板便不能正确的实例化。
  
-### 2.2.4. 模板的成员函数定义
+### 2.2.4. 模板的成员函数定义
 
-由于C++11正式废弃“模板导出”这一特性,因此在模板的变量在调用成员函数的时候,需要看到完整的成员函数定义。因此现在的模板中的成员函数,通常都是以内联的方式实现。
+由于C++11正式废弃“模板导出”这一特性,因此在模板的变量在调用成员函数的时候,需要看到完整的成员函数定义。因此现在的模板中的成员函数,通常都是以内联的方式实现。
 例如:
 
 ``` C++
@@ -302,9 +302,9 @@ void vector<T> /*看起来像偏特化*/ ::clear() // 函数的实现放在这
 
 ## 2.3. 函数模板 (Function Tempalte) 入门
 
-### 2.3.1. Function Template 的声明和定义
+### 2.3.1. 函数模板的声明和定义
 
-模板函数的语法与模板基本相同,也是以关键字`template`和模板参数列表作为声明与定义的开始。模板参数列表中的类型,可以出现在参数、返回值以及函数体中。比方说下面几个例子
+函数模板的语法与模板基本相同,也是以关键字`template`和模板参数列表作为声明与定义的开始。模板参数列表中的类型,可以出现在参数、返回值以及函数体中。比方说下面几个例子
 
 ```C++
 template <typename T> void foo(T const& v);
@@ -342,7 +342,7 @@ template <typename T> void foo()
 举个例子:generic typed function ‘add’
 ```
 
-在我遇到的朋友中,即便如此对他解释了模板,即便他了解了模板,也仍然会对模板产生畏难情绪。毕竟从形式上来说,模板类和模板函数都要较非模板的版本更加复杂,阅读代码所需要理解的内容也有所增多。
+在我遇到的朋友中,即便如此对他解释了模板,即便他了解了模板,也仍然会对模板产生畏难情绪。毕竟从形式上来说,模板化的类和模板化的函数都要较非模板的版本更加复杂,阅读代码所需要理解的内容也有所增多。
 
 如何才能克服这一问题,最终视模板如平坦代码呢?
 
@@ -356,7 +356,7 @@ template <typename T> void foo()
 
   3. 把解决方案用代码写出来。
 
-  4. 如果失败了,找到原因。是知识有盲点(例如不知道怎么将 `T&` 转化成 `T`),还是不可行(比如试图利用浮点常量特化模板,但实际上这样做是不可行的)?
+  4. 如果失败了,找到原因。是知识有盲点(例如不知道怎么将 `T&` 转化成 `T`),还是不可行(比如试图利用浮点常量特化模板,但实际上这样做是不可行的)?
 
 通过重复以上的练习,应该可以对模板的语法和含义都有所掌握。如果提出问题本身有困难,或许下面这个经典案例可以作为你思考的开始:
 
@@ -366,7 +366,7 @@ template <typename T> void foo()
 
 当然和“设计模式”一样,模板在实际应用中,也会有一些固定的需求和解决方案。比较常见的场景包括:泛型(最基本的用法)、通过类型获得相应的信息(型别萃取)、编译期间的计算、类型间的推导和变换(从一个类型变换成另外一个类型,比如boost::function)。这些本文在以后的章节中会陆续介绍。
 
-### 2.3.2. 模板函数的使用
+### 2.3.2. 函数模板的使用
 
 我们先来看一个简单的函数模板,两个数相加:
 
@@ -465,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);
@@ -1151,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这个成员。
 }
 ```
 
@@ -1491,7 +1491,7 @@ template <typename T> struct X {
 
 接下来,我们就来解决2.3.1节中留下的几个问题。
 
-先看第四个问题。为什么MSVC中,模板函数的定义内不管填什么编译器都不报错?因为MSVC在分析模板中成员函数定义时没有做任何事情。至于为啥连“大王叫我来巡山”都能过得去,这是C++语法/语义分析的特殊性导致的。
+先看第四个问题。为什么MSVC中,函数模板的定义内不管填什么编译器都不报错?因为MSVC在分析模板中成员函数定义时没有做任何事情。至于为啥连“大王叫我来巡山”都能过得去,这是C++语法/语义分析的特殊性导致的。
 C++是个非常复杂的语言,以至于它的编译器,不可能通过词法-语法-语义多趟分析清晰分割,因为它的语义将会直接干扰到语法:
 
 ```C++
@@ -1611,7 +1611,7 @@ error: variable has incomplete type 'A'
 1 error generated.
 ```
 
-符合标准的写法需要将模板的定义,和模板函数的定义分离开:
+符合标准的写法需要将模板的定义,和函数模板的定义分离开:
 
 > TODO 此处例子不够恰当,并且描述有歧义。需要在未来版本中修订。
 
@@ -2463,11 +2463,11 @@ void inc_counter(ICounter& counterObj);
 
 嗯,你说的没错,在这里这个特性一点都没用。
 
-这也提醒我们,当你觉得需要写 `enable_if` 的时候,首先要考虑到以下可能
+这也提醒我们,当你觉得需要写 `enable_if` 的时候,首先要考虑到以下可能的替代方案
 
-  * 重载(对模板函数
+  * 重载(适用于函数模板
   
-  * 偏特化(对模板类而言
+  * 偏特化(适用于类模板
   
   * 虚函数
    
@@ -2680,7 +2680,7 @@ void inc_counter(T& cnt);
 ```
 
 答案是:不能。
-因为`requires`作为关键字/保留字是存在二义性的。当它用于模板函数或者模板的声明时,它是一个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++就是这么的简(~~有~~)单(~~病~~)!