mirror of
https://github.com/wuye9036/CppTemplateTutorial.git
synced 2024-03-22 13:11:16 +08:00
Merge branch 'master' of https://github.com/wuye9036/CppTemplateTutorial
This commit is contained in:
commit
b871abfc72
|
@ -1,7 +1,8 @@
|
||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#define WRONG_CODE_ENABLED 1
|
#define WRONG_CODE_ENABLED 0
|
||||||
|
|
||||||
// 0. Basic Form
|
// 0. Basic Form
|
||||||
namespace _0
|
namespace _0
|
||||||
|
@ -92,6 +93,45 @@ namespace _1_2_2
|
||||||
// 1.3 Instanciating 2
|
// 1.3 Instanciating 2
|
||||||
namespace _1_3
|
namespace _1_3
|
||||||
{
|
{
|
||||||
|
template <int i> class A
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void foo()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
template <uint8_t a, typename b, void* c> class B {};
|
||||||
|
template <void (*a)()> class C {};
|
||||||
|
template <void (A<3>::*a)()> class D {};
|
||||||
|
|
||||||
|
#if WRONG_CODE_ENABLED
|
||||||
|
template <float a> class E {};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void foo()
|
||||||
|
{
|
||||||
|
A<5> a;
|
||||||
|
B<7, A<5>, nullptr> b;
|
||||||
|
C<&foo> c;
|
||||||
|
D<&A<3>::foo> d;
|
||||||
|
#if WRONG_CODE_ENABLED
|
||||||
|
int x = 3;
|
||||||
|
A<x> b;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WRONG_CODE_ENABLED
|
||||||
|
const char* s = "abc";
|
||||||
|
template <char const* s> class S
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
void foo2()
|
||||||
|
{
|
||||||
|
S<"abc"> i;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class ClassB
|
class ClassB
|
||||||
{
|
{
|
||||||
|
|
123
ReadMe.md
123
ReadMe.md
|
@ -1,4 +1,4 @@
|
||||||
|
|
||||||
# C++ Template 进阶指南
|
# C++ Template 进阶指南
|
||||||
|
|
||||||
## 0. 前言
|
## 0. 前言
|
||||||
|
@ -13,11 +13,11 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言,
|
||||||
|
|
||||||
但是实际上C++模板远没有想象的那么复杂。我们只需要换一个视角:在C++03的时候,模板本身就可以独立成为一门“语言”。它有“值”,有“函数”,有“表达式”和“语句”。除了语法比较蹩脚外,它既没有指针也没有数组,更没有C++里面复杂的继承和多态。可以说,它要比C语言要简单的多。如果我们把模板当做是一门语言来学习,那只需要花费学习OO零头的时间即可掌握。按照这样的思路,可以说在各种模板书籍中出现的多数技巧,都可以被轻松理解。
|
但是实际上C++模板远没有想象的那么复杂。我们只需要换一个视角:在C++03的时候,模板本身就可以独立成为一门“语言”。它有“值”,有“函数”,有“表达式”和“语句”。除了语法比较蹩脚外,它既没有指针也没有数组,更没有C++里面复杂的继承和多态。可以说,它要比C语言要简单的多。如果我们把模板当做是一门语言来学习,那只需要花费学习OO零头的时间即可掌握。按照这样的思路,可以说在各种模板书籍中出现的多数技巧,都可以被轻松理解。
|
||||||
|
|
||||||
简单回顾一下模板的历史。87年的时候,泛型(Generic Programming)变被纳入了C++的考虑范畴,并直接导致了后来模板语法的产生。可以说模板语法一开始就是为了在C++中提供泛型机制。92年的时候,Alexandar Stepanov开始研究利用模板语法制作程序库,后来这一程序库发展成STL,并在93年被接纳入标准中。
|
简单回顾一下模板的历史。87年的时候,泛型(Generic Programming)便被纳入了C++的考虑范畴,并直接导致了后来模板语法的产生。可以说模板语法一开始就是为了在C++中提供泛型机制。92年的时候,Alexander Stepanov开始研究利用模板语法制作程序库,后来这一程序库发展成STL,并在93年被接纳入标准中。
|
||||||
|
|
||||||
此时不少人以为STL已经是C++模板的集大成之作,C++模板技止于此。但是在95年的《C++ Report》上,John Barton和Lee Nackman提出了一个矩阵乘法的模板示例。可以说元编程在那个时候开始被很多人所关注。自此篇文章发表之后,很多人大牛都开始对模板产生了浓厚的兴趣。其中对元编程技法贡献最大的当属Alexandrescu的《Modern C++ Design》及模板程序库Loki。这一2001年发表的图书间接地导致了模板元编程库的出现。书中所使用的Typelist等泛型组件,和Policy等设计方法令人耳目一新。但是因为全书用的是近乎Geek的手法来构造一切设施,因此使得此书阅读起来略有难度。
|
此时不少人以为STL已经是C++模板的集大成之作,C++模板技止于此。但是在95年的《C++ Report》上,John Barton和Lee Nackman提出了一个矩阵乘法的模板示例。可以说元编程在那个时候开始被很多人所关注。自此篇文章发表之后,很多大牛都开始对模板产生了浓厚的兴趣。其中对元编程技法贡献最大的当属Alexandrescu的《Modern C++ Design》及模板程序库Loki。这一2001年发表的图书间接地导致了模板元编程库的出现。书中所使用的Typelist等泛型组件,和Policy等设计方法令人耳目一新。但是因为全书用的是近乎Geek的手法来构造一切设施,因此使得此书阅读起来略有难度。
|
||||||
|
|
||||||
2002年出版的另一本书《C++ Templates》,可以说是在Template方面的集大成制作。它详细阐述了模板的语法、提供了和模板有关的语言细节信息,举了很多有代表性例子。但是对于模板新手来说,这本书细节如此丰富,让他们随随便便就打了退堂鼓缴械投降。
|
2002年出版的另一本书《C++ Templates》,可以说是在Template方面的集大成之作。它详细阐述了模板的语法、提供了和模板有关的语言细节信息,举了很多有代表性例子。但是对于模板新手来说,这本书细节如此丰富,让他们随随便便就打了退堂鼓缴械投降。
|
||||||
|
|
||||||
本文的写作初衷,就是通过“编程语言”的视角,介绍一个简单、清晰的“模板语言”。我会尽可能的将模板的诸多要素连串起来,用一些简单的例子帮助读者学习这门“语言”,让读者在编写、阅读模板代码的时候,能像 `if(exp) { dosomething(); }`一样的信手拈来,让“模板元编程”技术成为读者牢固掌握、可举一反三的有用技能。
|
本文的写作初衷,就是通过“编程语言”的视角,介绍一个简单、清晰的“模板语言”。我会尽可能的将模板的诸多要素连串起来,用一些简单的例子帮助读者学习这门“语言”,让读者在编写、阅读模板代码的时候,能像 `if(exp) { dosomething(); }`一样的信手拈来,让“模板元编程”技术成为读者牢固掌握、可举一反三的有用技能。
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ template <typename T> class ClassA
|
||||||
void foo(int a);
|
void foo(int a);
|
||||||
```
|
```
|
||||||
|
|
||||||
`T`则可以类比为函数形参`a`,这里的“模板形参”`T`,也同函数形参一样取成任何你想要的名字;`typename`则类似于例子中函数参数类型`int`,它表示模板参数中的`T`将匹配一个类型。
|
`T`则可以类比为函数形参`a`,这里的“模板形参”`T`,也同函数形参一样取成任何你想要的名字;`typename`则类似于例子中函数参数类型`int`,它表示模板参数中的`T`将匹配一个类型。除了 `typename` 之外,我们再后面还要讲到,整型也可以作为模板的参数。
|
||||||
|
|
||||||
在定义完模板参数之后,便可以定义你所需要的类。不过在定义类的时候,除了一般类可以使用的类型外,你还可以使用在模板参数中使用的类型 `T`。可以说,这个 `T`是模板的精髓,因为你可以通过指定模板实参,将T替换成你所需要的类型。
|
在定义完模板参数之后,便可以定义你所需要的类。不过在定义类的时候,除了一般类可以使用的类型外,你还可以使用在模板参数中使用的类型 `T`。可以说,这个 `T`是模板的精髓,因为你可以通过指定模板实参,将T替换成你所需要的类型。
|
||||||
|
|
||||||
|
@ -198,7 +198,7 @@ void vector::clear()
|
||||||
因此,在成员函数实现的时候,必须要提供模板参数。此外,为什么类型名不是`vector`而是`vector<T>`呢?
|
因此,在成员函数实现的时候,必须要提供模板参数。此外,为什么类型名不是`vector`而是`vector<T>`呢?
|
||||||
如果你了解过模板的偏特化与特化的语法,应该能看出,这里的vector<T>在语法上类似于特化/偏特化。实际上,这里的函数定义也确实是成员函数的偏特化。特化和偏特化的概念,本文会在第二部分详细介绍。
|
如果你了解过模板的偏特化与特化的语法,应该能看出,这里的vector<T>在语法上类似于特化/偏特化。实际上,这里的函数定义也确实是成员函数的偏特化。特化和偏特化的概念,本文会在第二部分详细介绍。
|
||||||
|
|
||||||
最终,正确的成员函数实现如下所示:
|
综上,正确的成员函数实现如下所示:
|
||||||
|
|
||||||
``` C++
|
``` C++
|
||||||
template <typename T> // 模板参数
|
template <typename T> // 模板参数
|
||||||
|
@ -364,8 +364,7 @@ float a = GetValue(0); // 出错了!
|
||||||
int b = GetValue(1); // 也出错了!
|
int b = GetValue(1); // 也出错了!
|
||||||
```
|
```
|
||||||
|
|
||||||
为什么会出错呢?你仔细想了想,原来编译器是没办法去根据返回值推断类型的。函数调用的时候,返回值被谁接受还不知道呢。
|
为什么会出错呢?你仔细想了想,原来编译器是没办法去根据返回值推断类型的。函数调用的时候,返回值被谁接受还不知道呢。如下修改后,就一切正常了:
|
||||||
如下修改后,就一切正常了:
|
|
||||||
|
|
||||||
``` C++
|
``` C++
|
||||||
float a = GetValue<float>(0);
|
float a = GetValue<float>(0);
|
||||||
|
@ -374,14 +373,14 @@ int b = GetValue<int>(1);
|
||||||
|
|
||||||
嗯,是不是so easy啊?嗯,你又信心满满的做了一个练习:
|
嗯,是不是so easy啊?嗯,你又信心满满的做了一个练习:
|
||||||
|
|
||||||
你要写一个模板函数叫 `c_style_cast`,顾名思义,执行的是C风格的转换。然后出于方便起见,你希望它能和 `static_cast` 这样的内置转换有同样的写法。
|
你要写一个模板函数叫 `c_style_cast`,顾名思义,执行的是C风格的转换。然后出于方便起见,你希望它能和 `static_cast` 这样的内置转换有同样的写法。于是你写了一个use case。
|
||||||
于是你写了一个use case。
|
|
||||||
|
|
||||||
``` C++
|
``` C++
|
||||||
DstT dest = c_style_cast<DstT>(src);
|
DstT dest = c_style_cast<DstT>(src);
|
||||||
```
|
```
|
||||||
|
|
||||||
根据调用形式你知道了,有 `DstT` 和 `SrcT` 两个模板参数。参数只有一个, `src`,所以函数的形参当然是这么写了: `(SrcT src)`。实现也很简单, `(DstT)v`。
|
根据调用形式你知道了,有 `DstT` 和 `SrcT` 两个模板参数。参数只有一个, `src`,所以函数的形参当然是这么写了: `(SrcT src)`。实现也很简单, `(DstT)v`。
|
||||||
|
|
||||||
我们把手上得到的信息来拼一拼,就可以编写自己的函数模板了:
|
我们把手上得到的信息来拼一拼,就可以编写自己的函数模板了:
|
||||||
|
|
||||||
``` C++
|
``` C++
|
||||||
|
@ -400,8 +399,7 @@ float i = c_style_cast<float>(v);
|
||||||
error C2783: 'DstT _1_2_2::c_style_cast(SrcT)' : could not deduce template argument for 'DstT'
|
error C2783: 'DstT _1_2_2::c_style_cast(SrcT)' : could not deduce template argument for 'DstT'
|
||||||
```
|
```
|
||||||
|
|
||||||
然后你仔细的比较了一下,然后发现 … 模板参数有两个,而参数里面能得到的只有 `SrcT` 一个。结合出错信息看来关键在那个 `DstT` 上。
|
然后你仔细的比较了一下,然后发现 … 模板参数有两个,而参数里面能得到的只有 `SrcT` 一个。结合出错信息看来关键在那个 `DstT` 上。这个时候,你死马当活马医,把模板参数写完整了:
|
||||||
这个时候,你死马当活马医,把模板参数写完整了:
|
|
||||||
|
|
||||||
``` C++
|
``` C++
|
||||||
float i = c_style_cast<float, int>(v);
|
float i = c_style_cast<float, int>(v);
|
||||||
|
@ -410,7 +408,8 @@ float i = c_style_cast<float, int>(v);
|
||||||
嗯,很顺利的通过了。难道C++不能支持让参数推导一部分模板参数吗?
|
嗯,很顺利的通过了。难道C++不能支持让参数推导一部分模板参数吗?
|
||||||
|
|
||||||
当然是可以的。只不过在部分推导、部分指定的情况下,编译器对模版参数的顺序是有限制的:先写需要指定的模板参数,再把能推导出来的模板参数放在后面。
|
当然是可以的。只不过在部分推导、部分指定的情况下,编译器对模版参数的顺序是有限制的:先写需要指定的模板参数,再把能推导出来的模板参数放在后面。
|
||||||
在这个例子中,能推导出来的是 `SrcT`,需要指定的是 `DstT`。于是你把函数模板写成:
|
|
||||||
|
在这个例子中,能推导出来的是 `SrcT`,需要指定的是 `DstT`。把函数模板写成下面这样就可以了:
|
||||||
|
|
||||||
``` C++
|
``` C++
|
||||||
template <typename DstT, typename SrcT> DstT c_style_cast(SrcT v) // 模版参数 DstT 需要人肉指定,放前面。
|
template <typename DstT, typename SrcT> DstT c_style_cast(SrcT v) // 模版参数 DstT 需要人肉指定,放前面。
|
||||||
|
@ -422,13 +421,99 @@ int v = 0;
|
||||||
float i = c_style_cast<float>(v); // 形象地说,DstT会先把你指定的参数吃掉,剩下的就交给编译器从函数参数列表中推导啦。
|
float i = c_style_cast<float>(v); // 形象地说,DstT会先把你指定的参数吃掉,剩下的就交给编译器从函数参数列表中推导啦。
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
###1.3 整型也可是Template参数
|
###1.3 整型也可是Template参数
|
||||||
|
|
||||||
## 2. 模板世界的If-Then-Else:特化与偏特化
|
模板参数除了类型外(包括基本类型、结构、类类型等),也可以是一个整型数(Integral Number)。这里的整型数比较宽泛,包括布尔、不同位数、有无符号的整型,甚至包括指针。我们将整型的模板参数和类型作为模板参数来做一个对比:
|
||||||
###2.1 类模板的匹配规则:特化与部分特化
|
|
||||||
###2.2 函数模板的重载、参数匹配、特化与部分特化
|
``` C++
|
||||||
###2.3 技巧单元:模板与继承
|
template <typename T> class TemplateWithType;
|
||||||
|
template <int V> class TemplateWithValue;
|
||||||
|
```
|
||||||
|
|
||||||
|
我想这个时候你也更能理解 `typename` 的意思了:它相当于是模板参数的“类型”,告诉你 `T` 是一个 `typename`。
|
||||||
|
|
||||||
|
按照C++ Template最初的想法,模板不就是为了提供一个类型安全、易于调试的宏吗?有类型就够了,为什么要引入整型参数呢?考虑宏,它除了代码替换,还有一个作用是作为常数出现。所以整型模板参数最基本的用途,也是定义一个常数。例如这段代码的作用:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
template <typename T, int Size> struct Array
|
||||||
|
{
|
||||||
|
T data[Size];
|
||||||
|
};
|
||||||
|
|
||||||
|
Array<int, 16> arr;
|
||||||
|
```
|
||||||
|
|
||||||
|
便相当于下面这段代码:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
class IntArrayWithSize16
|
||||||
|
{
|
||||||
|
int data[16]; // int 替换了 T, 16 替换了 Size
|
||||||
|
};
|
||||||
|
|
||||||
|
IntArrayWithSize16 arr;
|
||||||
|
```
|
||||||
|
|
||||||
|
其中有一点要注意的是,因为模板的匹配是在编译的时候完成的,所以实例化模板的时候所使用的参数,也必须要在编译期就能确定。例如以下的例子编译器就会报错:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
template <int i> class A {};
|
||||||
|
|
||||||
|
void foo()
|
||||||
|
{
|
||||||
|
int x = 3;
|
||||||
|
A<5> a; // 正确!
|
||||||
|
A<x> b; // error C2971: '_1_3::A' : template parameter 'i' : 'x' : a local variable cannot be used as a non-type argument
|
||||||
|
}
|
||||||
|
```
|
||||||
|
因为x不是一个编译期常量,所以 `A<x>` 就会告诉你,x是一个局部变量,不能作为一个模板参数出现。
|
||||||
|
|
||||||
|
嗯,这里我们再来写几个相对复杂的例子:
|
||||||
|
|
||||||
|
``` C++
|
||||||
|
template <int i> class A
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void foo(int)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
template <uint8_t a, typename b, void* c> class B {};
|
||||||
|
template <bool, void (*a)()> class C {};
|
||||||
|
template <void (A<3>::*a)(int)> class D {};
|
||||||
|
|
||||||
|
template <int i> int Add(int a) // 当然也能用于函数模板
|
||||||
|
{
|
||||||
|
return a + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
void foo()
|
||||||
|
{
|
||||||
|
A<5> a;
|
||||||
|
B<
|
||||||
|
7, A<5>, nullptr
|
||||||
|
> b; // 模板参数可以是一个无符号八位整数,可以是模板生成的类;可以是一个指针。
|
||||||
|
C<false, &foo> c; // 模板参数可以是一个bool类型的常量,甚至可以是一个函数指针。
|
||||||
|
D<&A<3>::foo> d; // 丧心病狂啊!它还能是一个成员函数指针!
|
||||||
|
int x = Add<3>(5); // x == 8。因为整型模板参数无法从函数参数获得,所以只能是手工指定啦。
|
||||||
|
}
|
||||||
|
|
||||||
|
template <float a> class E {}; // ERROR: 别闹!早说过只能是整数类型的啦!
|
||||||
|
```
|
||||||
|
|
||||||
|
当然,除了单纯的用作常数之外,整型参数还有一些其它的用途。这些“其它”用途最重要的一点是让类型也可以像整数一样运算。《Modern C++ Design》给我们展示了很多这方面的例子。不过你不用急着去阅读那本天书,我们会在做好足够的知识铺垫后,让你轻松学会这些招数。
|
||||||
|
|
||||||
|
###1.4 模板形式与功能是统一的
|
||||||
|
|
||||||
|
第一章走马观花的带着大家复习了一下C++ Template的基本语法形式,也解释了包括 `typename` 在内,类/函数模板写法中各个语法元素的含义。形式是功能的外在体现,介绍它们也是为了让大家能理解到,模板之所以写成这种形式是有必要的,而不是语言的垃圾成分。
|
||||||
|
|
||||||
|
从下一章开始,我们便进入了更加复杂和丰富的世界:讨论模板的匹配规则。其中有令人望而生畏的特化与偏特化。但是,请相信我们在序言中所提到的:将模板作为一门语言来看待,它会变得有趣而简单。
|
||||||
|
|
||||||
|
## 2. 模板元编程基础
|
||||||
|
###2.1 编程,元编程,模板元编程
|
||||||
|
###2.2 模板世界的If-Then-Else:类模板的特化与偏特化
|
||||||
|
###2.3 函数模板的重载、参数匹配、特化与部分特化
|
||||||
|
###2.4 技巧单元:模板与继承
|
||||||
|
|
||||||
## 3 拿起特化的武器,去写程序吧!
|
## 3 拿起特化的武器,去写程序吧!
|
||||||
###3.1 利用模板特化规则实现If-Then-Else与Switch-Case
|
###3.1 利用模板特化规则实现If-Then-Else与Switch-Case
|
||||||
|
@ -464,4 +549,4 @@ alexandrescu 关于 min max 的讨论:《再谈Min和Max》
|
||||||
###7.4 Linq的C++实践
|
###7.4 Linq的C++实践
|
||||||
###7.5 更高更快更强:从Linq到FP
|
###7.5 更高更快更强:从Linq到FP
|
||||||
|
|
||||||
## 8 结语:讨论有益,争端无用
|
## 8 结语:讨论有益,争端无用
|
||||||
|
|
Loading…
Reference in New Issue
Block a user