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

This commit is contained in:
Ye Wu 2022-10-11 19:19:00 -07:00
parent c0e945d764
commit cd4232437e

View File

@ -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++就是这么的简(~~有~~)单(~~病~~