From 6d09b26f66b092553a64f4f2527f6e1f8e02774d Mon Sep 17 00:00:00 2001 From: Ye WU Date: Tue, 11 Feb 2014 23:40:23 -0800 Subject: [PATCH 01/99] =?UTF-8?q?1.2.2=20=E8=8A=82=EF=BC=8C=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E6=A8=A1=E6=9D=BF=E7=9A=84=E8=B0=83=E7=94=A8=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CppTemplateTutorial.cpp | 33 ++++++++- ReadMe.md | 149 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 180 insertions(+), 2 deletions(-) diff --git a/CppTemplateTutorial.cpp b/CppTemplateTutorial.cpp index 478e84d..d300dc5 100644 --- a/CppTemplateTutorial.cpp +++ b/CppTemplateTutorial.cpp @@ -1,7 +1,7 @@ #include "stdafx.h" #include -#define WRONG_CODE_ENABLED 0 +#define WRONG_CODE_ENABLED 1 // 0. Basic Form namespace _0 @@ -58,6 +58,37 @@ namespace _1_2 #endif } +// 1.2.2 + +namespace _1_2_2 +{ + template T Add(T a, T b) + { + return a + b; + } + + template DstT c_style_cast(SrcT v) + { + return (DstT)(v); + } + +#if WRONG_CODE_ENABLED + void foo() + { + int a = 0; + int b = 0; + char c = 0; + Add(b, c); + } + + void foo2() + { + int v = 0; + float i = c_style_cast(v); + } +#endif +} + // 1.3 Instanciating 2 namespace _1_3 { diff --git a/ReadMe.md b/ReadMe.md index 9104142..9df3a1f 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -271,11 +271,158 @@ template void foo() ####1.2.2 模板函数的使用 +我们先来看一个简单的函数模板,两个数相加: + +``` C++ +template T Add(T a, T b) +{ + return a + b; +} +``` + +函数模板的调用格式是: + +``` C++ +函数模板名 < 模板参数列表 > ( 参数 ) +``` + +例如,我们想对两个 `int` 求和,那么套用类的模板实例化方法,我们可以这么写: + +``` C++ +int a = 5; +int b = 3; +int result = Add(a, b); +``` + +这时我们等于拥有了一个新函数: + +``` C++ +int Add(int a, int b) { return a + b; } +``` + +这时在另外一个偏远的程序角落,你也需要求和。而此时你的参数类型是 `float` ,写做: + +``` C++ +Add(a, b); +``` + +一切看起来都很完美。但是如果你具备程序员的最优美德 ———— 懒惰 ———— 的话,你肯定会这样想,我在调用 `Add(a, b)` 的时候, `a` 和 `b` 匹配的都是那个 `T`。编译器就应该知道那个 `T` 实际上是 `int` 呀?为什么还要我多此一举写 `Add` 呢? +唔,我想说的是,编译器的作者也是这么想的。所以实际上你在编译器里面写下以下片段: + +``` C++ +int a = 5; +int b = 3; +int result = Add(a, b); +``` + +编译器会心领神会的将 `Add` 变成 `Add`。但是编译器并不会精神分裂。如果你这么写的话呢? + +``` C++ +int a = 5; +char b = 3; +int result = Add(a, b); +``` + +第一个参数 `a` 告诉编译器,这个 `T` 是 `int`。编译器点点头说,好。但是第二个参数 `b` 不高兴了,告诉编译器说,你这个 `T`,其实是 `char`。 +两个参数各自指导 `T` 的类型,编译器就不知道怎么做了。在Visual Studio 2012下,会有这样的提示: + +``` +error C2782: 'T _1_2_2::Add(T,T)' : template parameter 'T' is ambiguous +``` + +这个提示再明确不过了。 + +不过,只要你别逼得编译器精神分裂的话,编译器其实是非常聪明的,它可以从很多的蛛丝马迹中,猜测到你真正的意图,有如下面的例子: + +``` +template class A {}; + +template T foo( A v ); + +A v; +foo(v); // 它能准确的猜到 T 是 int. +``` + +咦,编译器居然绕过了A这个外套,猜到了 `T` 匹配的是 `int`。编译器是怎么完成这一“魔法”的,我们暂且不表,2.2节时再和盘托出。 + +下面轮到你的练习时间了。你试着写了很多的例子,但是其中一个你还是犯了疑惑: + +``` C++ +float data[1024]; + +template T GetValue(int i) +{ + return static_cast(data[i]); +} + +float a = GetValue(0); +int b = GetValue(1); +``` + +为什么会出错呢?你仔细想了想,原来编译器是没办法去根据返回值推断类型的。函数调用的时候,返回值被谁接受还不知道呢。 +如下修改后,就一切正常了: + +``` C++ +float a = GetValue(0); +int b = GetValue(1); +``` + +嗯,是不是so easy啊?嗯,你又信心满满的做了一个练习: + +你要写一个模板函数叫 `c_style_cast`,顾名思义,执行的是C风格的转换。然后出于方便起见,你希望它能和 `static_cast` 这样的内置转换有同样的写法。 +于是你写了一个use case。 + +``` +DstT dest = c_style_cast(src); +``` + +根据调用形式你知道了,有 `DstT` 和 `SrcT` 两个模板参数。参数只有一个, `src`,所以函数的形参当然是这么写了: `(SrcT src)`。实现也很简单, `(DstT)v`。 +我们把手上得到的信息来拼一拼,就可以编写自己的函数模板了: + +``` +template DstT c_style_cast(SrcT v) +{ + return (DstT)(v); +} + +int v = 0; +float i = c_style_cast(v); +``` + +嗯,很Easy嘛!我们F6一下…咦!这是什么意思! + +``` +error C2783: 'DstT _1_2_2::c_style_cast(SrcT)' : could not deduce template argument for 'DstT' +``` + +然后你仔细的比较了一下,然后发现 … 模板参数有两个,而参数里面能得到的只有 `SrcT` 只有一个。结合出错信息看来关键在那个 `DstT` 上。 +这个时候,你死马当活马医,把模板参数写完整了: + +``` +float i = c_style_cast(v); +``` + +嗯,很顺利的通过了。难道C++不能支持让参数推导一部分模板参数吗? + +当然是可以的。只不过在部分推导、部分指定的情况下,编译器对模版参数的顺序是有限制的:先写需要指定的模板参数,再把能推导出来的模板参数放在后面。 +在这个例子中,能推导出来的是 `SrcT`,需要指定的是 `DstT`。于是你把函数模板写成: + +``` +template DstT c_style_cast(SrcT v) // 模版参数 DstT 需要人肉指定,放前面。 +{ + return (DstT)(v); +} + +int v = 0; +float i = c_style_cast(v); // 形象地说,DstT会先把你指定的参数吃掉,剩下的就交给编译器从函数参数列表中推导啦。 +``` + + ###1.3 整型也可是Template参数 ## 2. 模板世界的If-Then-Else:特化与偏特化 ###2.1 类模板的匹配规则:特化与部分特化 -###2.2 函数模板的重载、特化与部分特化 +###2.2 函数模板的重载、参数匹配、特化与部分特化 ###2.3 技巧单元:模板与继承 ## 3 拿起特化的武器,去写程序吧! From 6f0260d4e427f634056ab3c98a2975f33caf8696 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 11 Feb 2014 23:44:16 -0800 Subject: [PATCH 02/99] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BA=861.2.2=E8=8A=82?= =?UTF-8?q?=E4=B8=AD=E4=BB=A3=E7=A0=81=E6=A0=BC=E5=BC=8F=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 9df3a1f..469cbde 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -334,7 +334,7 @@ error C2782: 'T _1_2_2::Add(T,T)' : template parameter 'T' is ambiguous 不过,只要你别逼得编译器精神分裂的话,编译器其实是非常聪明的,它可以从很多的蛛丝马迹中,猜测到你真正的意图,有如下面的例子: -``` +``` C++ template class A {}; template T foo( A v ); @@ -372,14 +372,14 @@ int b = GetValue(1); 你要写一个模板函数叫 `c_style_cast`,顾名思义,执行的是C风格的转换。然后出于方便起见,你希望它能和 `static_cast` 这样的内置转换有同样的写法。 于是你写了一个use case。 -``` +``` C++ DstT dest = c_style_cast(src); ``` 根据调用形式你知道了,有 `DstT` 和 `SrcT` 两个模板参数。参数只有一个, `src`,所以函数的形参当然是这么写了: `(SrcT src)`。实现也很简单, `(DstT)v`。 我们把手上得到的信息来拼一拼,就可以编写自己的函数模板了: -``` +``` C++ template DstT c_style_cast(SrcT v) { return (DstT)(v); @@ -391,14 +391,14 @@ float i = c_style_cast(v); 嗯,很Easy嘛!我们F6一下…咦!这是什么意思! -``` +``` C++ error C2783: 'DstT _1_2_2::c_style_cast(SrcT)' : could not deduce template argument for 'DstT' ``` 然后你仔细的比较了一下,然后发现 … 模板参数有两个,而参数里面能得到的只有 `SrcT` 只有一个。结合出错信息看来关键在那个 `DstT` 上。 这个时候,你死马当活马医,把模板参数写完整了: -``` +``` C++ float i = c_style_cast(v); ``` @@ -407,7 +407,7 @@ float i = c_style_cast(v); 当然是可以的。只不过在部分推导、部分指定的情况下,编译器对模版参数的顺序是有限制的:先写需要指定的模板参数,再把能推导出来的模板参数放在后面。 在这个例子中,能推导出来的是 `SrcT`,需要指定的是 `DstT`。于是你把函数模板写成: -``` +``` C++ template DstT c_style_cast(SrcT v) // 模版参数 DstT 需要人肉指定,放前面。 { return (DstT)(v); From 2937409a90dbeb1a705bd0d126c27bf4f3a3c409 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 11 Feb 2014 23:53:47 -0800 Subject: [PATCH 03/99] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=860.4=E8=8A=82?= =?UTF-8?q?=EF=BC=9A=E8=A1=A5=E9=81=97=E5=92=8C=E5=86=99=E4=BD=9C=E8=AE=A1?= =?UTF-8?q?=E5=88=92=E3=80=82=201.2.2=20=E4=BF=AE=E6=AD=A3=E4=BA=86?= =?UTF-8?q?=E9=83=A8=E5=88=86=E8=A1=8C=E6=96=87=E9=94=99=E8=AF=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 469cbde..8ed63c2 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -39,6 +39,10 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言, 全文所有为我所撰写的部分,作者均保留所有版权。如果有需要转帖或引用,还请注明出处并告知于我。 +###0.4 补遗、写作计划 + +1. 需要增加一节:模板的使用动机。 + ## 1. Template的基本语法 ###1.1 Template Class基本语法 @@ -300,13 +304,13 @@ int result = Add(a, b); int Add(int a, int b) { return a + b; } ``` -这时在另外一个偏远的程序角落,你也需要求和。而此时你的参数类型是 `float` ,写做: +这时在另外一个偏远的程序角落,你也需要求和。而此时你的参数类型是 `float` ,于是你写下: ``` C++ Add(a, b); ``` -一切看起来都很完美。但是如果你具备程序员的最优美德 ———— 懒惰 ———— 的话,你肯定会这样想,我在调用 `Add(a, b)` 的时候, `a` 和 `b` 匹配的都是那个 `T`。编译器就应该知道那个 `T` 实际上是 `int` 呀?为什么还要我多此一举写 `Add` 呢? +一切看起来都很完美。但如果你具备程序员的最佳美德——懒惰——的话,你肯定会这样想,我在调用 `Add(a, b)` 的时候, `a` 和 `b` 匹配的都是那个 `T`。编译器就应该知道那个 `T` 实际上是 `int` 呀?为什么还要我多此一举写 `Add` 呢? 唔,我想说的是,编译器的作者也是这么想的。所以实际上你在编译器里面写下以下片段: ``` C++ @@ -315,7 +319,7 @@ int b = 3; int result = Add(a, b); ``` -编译器会心领神会的将 `Add` 变成 `Add`。但是编译器并不会精神分裂。如果你这么写的话呢? +编译器会心领神会的将 `Add` 变成 `Add`。但是编译器不能面对模棱两可的答案。比如你这么写的话呢? ``` C++ int a = 5; @@ -330,7 +334,7 @@ int result = Add(a, b); error C2782: 'T _1_2_2::Add(T,T)' : template parameter 'T' is ambiguous ``` -这个提示再明确不过了。 +好吧,"ambigous",这个提示再明确不过了。 不过,只要你别逼得编译器精神分裂的话,编译器其实是非常聪明的,它可以从很多的蛛丝马迹中,猜测到你真正的意图,有如下面的例子: @@ -340,7 +344,7 @@ template class A {}; template T foo( A v ); A v; -foo(v); // 它能准确的猜到 T 是 int. +foo(v); // 它能准确的猜到 T 是 int. ``` 咦,编译器居然绕过了A这个外套,猜到了 `T` 匹配的是 `int`。编译器是怎么完成这一“魔法”的,我们暂且不表,2.2节时再和盘托出。 @@ -355,8 +359,8 @@ template T GetValue(int i) return static_cast(data[i]); } -float a = GetValue(0); -int b = GetValue(1); +float a = GetValue(0); // 出错了! +int b = GetValue(1); // 也出错了! ``` 为什么会出错呢?你仔细想了想,原来编译器是没办法去根据返回值推断类型的。函数调用的时候,返回值被谁接受还不知道呢。 @@ -395,7 +399,7 @@ float i = c_style_cast(v); error C2783: 'DstT _1_2_2::c_style_cast(SrcT)' : could not deduce template argument for 'DstT' ``` -然后你仔细的比较了一下,然后发现 … 模板参数有两个,而参数里面能得到的只有 `SrcT` 只有一个。结合出错信息看来关键在那个 `DstT` 上。 +然后你仔细的比较了一下,然后发现 … 模板参数有两个,而参数里面能得到的只有 `SrcT` 一个。结合出错信息看来关键在那个 `DstT` 上。 这个时候,你死马当活马医,把模板参数写完整了: ``` C++ @@ -414,7 +418,7 @@ template DstT c_style_cast(SrcT v) // 模版参 } int v = 0; -float i = c_style_cast(v); // 形象地说,DstT会先把你指定的参数吃掉,剩下的就交给编译器从函数参数列表中推导啦。 +float i = c_style_cast(v); // 形象地说,DstT会先把你指定的参数吃掉,剩下的就交给编译器从函数参数列表中推导啦。 ``` From 480d599cb9448dd0c6873e2f04b92e1c8deb074a Mon Sep 17 00:00:00 2001 From: Wu Ye Date: Wed, 12 Feb 2014 23:29:18 -0800 Subject: [PATCH 04/99] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=861.3=E8=8A=82?= =?UTF-8?q?=EF=BC=88=E6=95=B4=E5=9E=8B=E4=B9=9F=E5=8F=AF=E4=BB=A5=E4=BD=9C?= =?UTF-8?q?=E4=B8=BA=E6=A8=A1=E6=9D=BF=E5=8F=82=E6=95=B0=EF=BC=89=E5=92=8C?= =?UTF-8?q?1.4=E8=8A=82=EF=BC=88=E6=A8=A1=E6=9D=BF=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=92=8C=E5=BD=A2=E5=BC=8F=E7=9A=84=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=EF=BC=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CppTemplateTutorial.cpp | 28 ++++++++++- ReadMe.md | 101 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 118 insertions(+), 11 deletions(-) diff --git a/CppTemplateTutorial.cpp b/CppTemplateTutorial.cpp index d300dc5..fbd07ec 100644 --- a/CppTemplateTutorial.cpp +++ b/CppTemplateTutorial.cpp @@ -1,7 +1,8 @@ #include "stdafx.h" #include +#include -#define WRONG_CODE_ENABLED 1 +#define WRONG_CODE_ENABLED 0 // 0. Basic Form namespace _0 @@ -92,6 +93,31 @@ namespace _1_2_2 // 1.3 Instanciating 2 namespace _1_3 { + template class A + { + public: + void foo() + { + } + }; + template class B {}; + template class C {}; + template ::*a)()> class D {}; + template class E {}; + 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 b; +#endif + } + + + template class ClassB { diff --git a/ReadMe.md b/ReadMe.md index 8ed63c2..a25b9f4 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -69,7 +69,7 @@ template class ClassA void foo(int a); ``` -`T`则可以类比为函数形参`a`,这里的“模板形参”`T`,也同函数形参一样取成任何你想要的名字;`typename`则类似于例子中函数参数类型`int`,它表示模板参数中的`T`将匹配一个类型。 +`T`则可以类比为函数形参`a`,这里的“模板形参”`T`,也同函数形参一样取成任何你想要的名字;`typename`则类似于例子中函数参数类型`int`,它表示模板参数中的`T`将匹配一个类型。除了 `typename` 之外,我们再后面还要讲到,整型也可以作为模板的参数。 在定义完模板参数之后,便可以定义你所需要的类。不过在定义类的时候,除了一般类可以使用的类型外,你还可以使用在模板参数中使用的类型 `T`。可以说,这个 `T`是模板的精髓,因为你可以通过指定模板实参,将T替换成你所需要的类型。 @@ -197,7 +197,7 @@ void vector::clear() 因此,在成员函数实现的时候,必须要提供模板参数。此外,为什么类型名不是`vector`而是`vector`呢? 如果你了解过模板的偏特化与特化的语法,应该能看出,这里的vector在语法上类似于特化/偏特化。实际上,这里的函数定义也确实是成员函数的偏特化。特化和偏特化的概念,本文会在第二部分详细介绍。 -最终,正确的成员函数实现如下所示: +综上,正确的成员函数实现如下所示: ``` C++ template // 模板参数 @@ -363,8 +363,7 @@ float a = GetValue(0); // 出错了! int b = GetValue(1); // 也出错了! ``` -为什么会出错呢?你仔细想了想,原来编译器是没办法去根据返回值推断类型的。函数调用的时候,返回值被谁接受还不知道呢。 -如下修改后,就一切正常了: +为什么会出错呢?你仔细想了想,原来编译器是没办法去根据返回值推断类型的。函数调用的时候,返回值被谁接受还不知道呢。如下修改后,就一切正常了: ``` C++ float a = GetValue(0); @@ -373,14 +372,14 @@ 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); ``` 根据调用形式你知道了,有 `DstT` 和 `SrcT` 两个模板参数。参数只有一个, `src`,所以函数的形参当然是这么写了: `(SrcT src)`。实现也很简单, `(DstT)v`。 + 我们把手上得到的信息来拼一拼,就可以编写自己的函数模板了: ``` C++ @@ -399,8 +398,7 @@ float i = c_style_cast(v); error C2783: 'DstT _1_2_2::c_style_cast(SrcT)' : could not deduce template argument for 'DstT' ``` -然后你仔细的比较了一下,然后发现 … 模板参数有两个,而参数里面能得到的只有 `SrcT` 一个。结合出错信息看来关键在那个 `DstT` 上。 -这个时候,你死马当活马医,把模板参数写完整了: +然后你仔细的比较了一下,然后发现 … 模板参数有两个,而参数里面能得到的只有 `SrcT` 一个。结合出错信息看来关键在那个 `DstT` 上。这个时候,你死马当活马医,把模板参数写完整了: ``` C++ float i = c_style_cast(v); @@ -409,7 +407,8 @@ float i = c_style_cast(v); 嗯,很顺利的通过了。难道C++不能支持让参数推导一部分模板参数吗? 当然是可以的。只不过在部分推导、部分指定的情况下,编译器对模版参数的顺序是有限制的:先写需要指定的模板参数,再把能推导出来的模板参数放在后面。 -在这个例子中,能推导出来的是 `SrcT`,需要指定的是 `DstT`。于是你把函数模板写成: + +在这个例子中,能推导出来的是 `SrcT`,需要指定的是 `DstT`。把函数模板写成下面这样就可以了: ``` C++ template DstT c_style_cast(SrcT v) // 模版参数 DstT 需要人肉指定,放前面。 @@ -421,9 +420,91 @@ int v = 0; float i = c_style_cast(v); // 形象地说,DstT会先把你指定的参数吃掉,剩下的就交给编译器从函数参数列表中推导啦。 ``` - ###1.3 整型也可是Template参数 +模板参数除了类型外(包括基本类型、结构、类类型等),也可以是一个整型数(Integral Number)。这里的整型数比较宽泛,包括布尔、不同位数、有无符号的整型,甚至包括指针。我们将整型的模板参数和类型作为模板参数来做一个对比: + +``` C++ +template class TemplateWithType; +template class TemplateWithValue; +``` + +我想这个时候你也更能理解 `typename` 的意思了:它相当于是模板参数的“类型”,告诉你 `T` 是一个 `typename`。 + +按照C++ Template最初的想法,模板不就是为了提供一个类型安全、易于调试的宏吗?有类型就够了,为什么要引入整型参数呢?考虑宏,它除了代码替换,还有一个作用是作为常数出现。所以整型模板参数最基本的用途,也是定义一个常数。例如这段代码的作用: + +``` C++ +template struct Array +{ + T data[Size]; +}; + +Array arr; +``` + +便相当于下面这段代码: + +``` C++ +class IntArrayWithSize16 +{ + int data[16]; // int 替换了 T, 16 替换了 Size +}; + +IntArrayWithSize16 arr; +``` + +其中有一点要注意的是,因为模板的匹配是在编译的时候完成的,所以实例化模板的时候所使用的参数,也必须要在编译期就能确定。例如以下的例子编译器就会报错: + +``` C++ +template class A {}; + +void foo() +{ + int x = 3; + A<5> a; // 正确! + A b; // error C2971: '_1_3::A' : template parameter 'i' : 'x' : a local variable cannot be used as a non-type argument +} +``` +因为x不是一个编译期常量,所以 `A` 就会告诉你,x是一个局部变量,不能作为一个模板参数出现。 + +嗯,这里我们再写几个相对复杂的例子: + +``` C++ +template class A +{ +public: + void foo(int) + { + } +}; +template class B {}; +template class C {}; +template ::*a)(int)> class D {}; + +template int Add(int a) // 当然也能用于函数模板 +{ + return a + i; +} + +void foo() +{ + A<5> a; + B< + 7, A<5>, nullptr, false + > b; // 模板参数可以是一个无符号八位整数,可以是模板生成的类;可以是一个指针。 + C c; // 模板参数可以是一个bool类型的常量,甚至可以是一个函数指针。 + D<&A<3>::foo> d; // 丧心病狂啊!它还能是一个成员函数指针! + int x = Add<3>(5); // x == 8。因为整型模板参数无法从函数参数获得,所以只能是手工指定啦。 +} + +template class E {}; // ERROR: 别闹!早说过只能是整数类型的啦! +``` + +###1.4 模板形式与功能是统一的 +第一章走马观花的带着大家复习了一下C++ Template的基本语法形式,也解释了包括 `typename` 在内,类/函数模板写法中各个语法元素的含义。形式是功能的外在体现,介绍它们也是为了让大家能理解到,模板之所以写成这种形式是有必要的,而不是语言的垃圾成分。 + +从下一章开始,我们便进入了更加复杂和丰富的世界:讨论模板的匹配规则。其中有令人望而生畏的特化与偏特化。但是,请相信我们在序言中所提到的:将模板作为一门语言来看待,它会变得有趣而简单。 + ## 2. 模板世界的If-Then-Else:特化与偏特化 ###2.1 类模板的匹配规则:特化与部分特化 ###2.2 函数模板的重载、参数匹配、特化与部分特化 From 50b2a8cffbd53c09cef2c8c10f2ac6279ab56ff2 Mon Sep 17 00:00:00 2001 From: Wu Ye Date: Wed, 12 Feb 2014 23:44:12 -0800 Subject: [PATCH 05/99] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=861.3=E8=8A=82?= =?UTF-8?q?=E4=B8=80=E5=A4=84=E7=A4=BA=E4=BE=8B=E4=BB=A3=E7=A0=81=E7=9A=84?= =?UTF-8?q?=E9=94=99=E8=AF=AF=EF=BC=8C=E8=A1=A5=E5=85=85=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E6=96=87=E5=AD=97=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index a25b9f4..234c221 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -467,7 +467,7 @@ void foo() ``` 因为x不是一个编译期常量,所以 `A` 就会告诉你,x是一个局部变量,不能作为一个模板参数出现。 -嗯,这里我们再写几个相对复杂的例子: +嗯,这里我们再来写几个相对复杂的例子: ``` C++ template class A @@ -490,7 +490,7 @@ void foo() { A<5> a; B< - 7, A<5>, nullptr, false + 7, A<5>, nullptr > b; // 模板参数可以是一个无符号八位整数,可以是模板生成的类;可以是一个指针。 C c; // 模板参数可以是一个bool类型的常量,甚至可以是一个函数指针。 D<&A<3>::foo> d; // 丧心病狂啊!它还能是一个成员函数指针! @@ -500,7 +500,10 @@ void foo() template class E {}; // ERROR: 别闹!早说过只能是整数类型的啦! ``` +当然,除了单纯的用作常数之外,整型参数还有一些其它的用途。这些“其它”用途最重要的一点是让类型也可以像整数一样运算。《Modern C++ Design》给我们展示了很多这方面的例子。不过你不用急着去阅读那本天书,我们会在做好足够的知识铺垫后,让你轻松学会这些招数。 + ###1.4 模板形式与功能是统一的 + 第一章走马观花的带着大家复习了一下C++ Template的基本语法形式,也解释了包括 `typename` 在内,类/函数模板写法中各个语法元素的含义。形式是功能的外在体现,介绍它们也是为了让大家能理解到,模板之所以写成这种形式是有必要的,而不是语言的垃圾成分。 从下一章开始,我们便进入了更加复杂和丰富的世界:讨论模板的匹配规则。其中有令人望而生畏的特化与偏特化。但是,请相信我们在序言中所提到的:将模板作为一门语言来看待,它会变得有趣而简单。 From 308eb2f2e3d5d18fe8ba29c8c29b60343f4c8c9f Mon Sep 17 00:00:00 2001 From: tianfei Date: Fri, 14 Feb 2014 13:18:56 +0800 Subject: [PATCH 06/99] Fixed some words --- ReadMe.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 234c221..411daf4 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -13,9 +13,9 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言, 但是实际上C++模板远没有想象的那么复杂。我们只需要换一个视角:在C++03的时候,模板本身就可以独立成为一门“语言”。它有“值”,有“函数”,有“表达式”和“语句”。除了语法比较蹩脚外,它既没有指针也没有数组,更没有C++里面复杂的继承和多态。可以说,它要比C语言要简单的多。如果我们把模板当做是一门语言来学习,那只需要花费学习OO零头的时间即可掌握。按照这样的思路,可以说在各种模板书籍中出现的多数技巧,都可以被轻松理解。 -简单回顾一下模板的历史。87年的时候,泛型(Generic Programming)变被纳入了C++的考虑范畴,并直接导致了后来模板语法的产生。可以说模板语法一开始就是为了在C++中提供泛型机制。92年的时候,Alexandar Stepanov开始研究利用模板语法制作程序库,后来这一程序库发展成STL,并在93年被接纳入标准中。 +简单回顾一下模板的历史。87年的时候,泛型(Generic Programming)便被纳入了C++的考虑范畴,并直接导致了后来模板语法的产生。可以说模板语法一开始就是为了在C++中提供泛型机制。92年的时候,Alexandar 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方面的集大成制作。它详细阐述了模板的语法、提供了和模板有关的语言细节信息,举了很多有代表性例子。但是对于模板新手来说,这本书细节如此丰富,让他们随随便便就打了退堂鼓缴械投降。 @@ -547,4 +547,4 @@ alexandrescu 关于 min max 的讨论:《再谈Min和Max》 ###7.4 Linq的C++实践 ###7.5 更高更快更强:从Linq到FP -## 8 结语:讨论有益,争端无用 \ No newline at end of file +## 8 结语:讨论有益,争端无用 From e66f371182ea0d759f41e2f14cf9005266a9db06 Mon Sep 17 00:00:00 2001 From: Wu Ye Date: Thu, 13 Feb 2014 22:13:50 -0800 Subject: [PATCH 07/99] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=AC=AC=E4=BA=8C?= =?UTF-8?q?=E7=AB=A0=E5=B0=8F=E8=8A=82=E5=90=8D=E7=A7=B0=EF=BC=9B=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E4=BA=86=E7=A4=BA=E4=BE=8B=E4=BB=A3=E7=A0=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CppTemplateTutorial.cpp | 14 ++++++++++++++ ReadMe.md | 9 +++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/CppTemplateTutorial.cpp b/CppTemplateTutorial.cpp index fbd07ec..55275a9 100644 --- a/CppTemplateTutorial.cpp +++ b/CppTemplateTutorial.cpp @@ -103,7 +103,11 @@ namespace _1_3 template class B {}; template class C {}; template ::*a)()> class D {}; + +#if WRONG_CODE_ENABLED template class E {}; +#endif + void foo() { A<5> a; @@ -116,7 +120,17 @@ namespace _1_3 #endif } +#if WRONG_CODE_ENABLED + const char* s = "abc"; + template class S + { + }; + void foo2() + { + S<"abc"> i; + } +#endif template class ClassB diff --git a/ReadMe.md b/ReadMe.md index 234c221..775a498 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -508,10 +508,11 @@ template class E {}; // ERROR: 别闹!早说过只能是整数类 从下一章开始,我们便进入了更加复杂和丰富的世界:讨论模板的匹配规则。其中有令人望而生畏的特化与偏特化。但是,请相信我们在序言中所提到的:将模板作为一门语言来看待,它会变得有趣而简单。 -## 2. 模板世界的If-Then-Else:特化与偏特化 -###2.1 类模板的匹配规则:特化与部分特化 -###2.2 函数模板的重载、参数匹配、特化与部分特化 -###2.3 技巧单元:模板与继承 +## 2. 模板元编程基础 +###2.1 编程,元编程,模板元编程 +###2.2 模板世界的If-Then-Else:类模板的特化与偏特化 +###2.3 函数模板的重载、参数匹配、特化与部分特化 +###2.4 技巧单元:模板与继承 ## 3 拿起特化的武器,去写程序吧! ###3.1 利用模板特化规则实现If-Then-Else与Switch-Case From 76d4d32e53738f6b78f8358bcc09188219b0c6b7 Mon Sep 17 00:00:00 2001 From: jTux Date: Tue, 18 Feb 2014 23:42:28 +0800 Subject: [PATCH 08/99] fix typos --- ReadMe.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 03393f3..0789a85 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,4 +1,4 @@ - + # C++ Template 进阶指南 ## 0. 前言 @@ -13,11 +13,11 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言, 但是实际上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的手法来构造一切设施,因此使得此书阅读起来略有难度。 -2002年出版的另一本书《C++ Templates》,可以说是在Template方面的集大成制作。它详细阐述了模板的语法、提供了和模板有关的语言细节信息,举了很多有代表性例子。但是对于模板新手来说,这本书细节如此丰富,让他们随随便便就打了退堂鼓缴械投降。 +2002年出版的另一本书《C++ Templates》,可以说是在Template方面的集大成之作。它详细阐述了模板的语法、提供了和模板有关的语言细节信息,举了很多有代表性例子。但是对于模板新手来说,这本书细节如此丰富,让他们随随便便就打了退堂鼓缴械投降。 本文的写作初衷,就是通过“编程语言”的视角,介绍一个简单、清晰的“模板语言”。我会尽可能的将模板的诸多要素连串起来,用一些简单的例子帮助读者学习这门“语言”,让读者在编写、阅读模板代码的时候,能像 `if(exp) { dosomething(); }`一样的信手拈来,让“模板元编程”技术成为读者牢固掌握、可举一反三的有用技能。 From 3d40779b96e15aacfde55abd6e14e16f148ee2b0 Mon Sep 17 00:00:00 2001 From: Ye WU Date: Tue, 18 Feb 2014 11:13:06 -0800 Subject: [PATCH 09/99] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BA=860.4=E8=8A=82?= =?UTF-8?q?=EF=BC=8C=E5=BB=BA=E8=AE=AE=E7=AD=89=E5=86=85=E5=AE=B9=E9=83=BD?= =?UTF-8?q?=E5=BD=92=E7=BD=AE=E4=BA=8E=E6=AD=A4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 8ed63c2..1b02cbb 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -39,9 +39,10 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言, 全文所有为我所撰写的部分,作者均保留所有版权。如果有需要转帖或引用,还请注明出处并告知于我。 -###0.4 补遗、写作计划 +###0.4 意见、建议、喷、补遗、写作计划 -1. 需要增加一节:模板的使用动机。 +* 需增加:模板的使用动机。 +* 建议:比较模板和函数的差异性 ## 1. Template的基本语法 From f780813521f66b89c4a25ab966962fd9ff2c27eb Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 27 Feb 2014 12:38:54 -0800 Subject: [PATCH 10/99] =?UTF-8?q?0.4=E8=8A=82=E5=A2=9E=E5=8A=A0=E5=86=85?= =?UTF-8?q?=E5=AE=B9=EF=BC=8C=E8=B0=83=E6=95=B4=E6=A0=BC=E5=BC=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 9aa9a0d..5ec3d48 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -41,8 +41,11 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言, ###0.4 意见、建议、喷、补遗、写作计划 -* 需增加:模板的使用动机。 -* 建议:比较模板和函数的差异性 +* 需增加: + * 模板的使用动机。 +* 建议: + * 比较模板和函数的差异性 + * 蓝色:C++14 Return type deduction for normal functions 的分析 ## 1. Template的基本语法 From c3a9257a8a5ba05eb33b2bcf5b9e9773ea34247d Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 27 Feb 2014 14:00:11 -0800 Subject: [PATCH 11/99] =?UTF-8?q?=E6=92=B0=E5=86=992.1=E8=8A=82=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index 5ec3d48..ea62313 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -514,6 +514,71 @@ template class E {}; // ERROR: 别闹!早说过只能是整数类 ## 2. 模板元编程基础 ###2.1 编程,元编程,模板元编程 + +技术的学习是一个登山的过程。第一章是最为平坦的山脚道路。而从这一章开始,则是正式的爬坡。无论是我写作还是你阅读,都需要付出比第一章更多的代价。那么问题就是,付出更多的精力学习模板是否值得? + +这个问题很功利,但是一阵见血。因为技术的根本目的在于解决需求。那C++的模板能做什么? + +一个高(树)大(新)上(蜂)的回答是,C++里面的模板,犹如C中的宏、C#和Java中的自省(restropection)和反射(reflection)一样,是一个改变语言内涵,拓展语言外延的存在。 + +程序最根本的目的是什么?复现真实世界或人所构想的规律,减少重复工作的成本,或通过提升规模完成人所不能及之事。但是世间之事万千,有限的程序如何重现复杂的世界呢? + +答案是“抽象”。论及具体手段,无外乎“求同”与“存异”:概括一般规律,处理特殊情况。这也是软件工程所追求的目标。一般规律概括的越好,我们所付出的劳动也就越少。 + +同样的,作为脑力劳动的产品,程序本身也是有规律性的。《Modern C++ Design》中的前言就抛出了一连串有代表性的问题: + +``` +如何撰写更高级的C++程式? +如何应付即使在很干净的设计中仍然像雪崩一样的不相干细节? +如何构建可复用组件,使得每次在不同程式中应用组件时无需大动干戈? +``` + +我们以数据结构举例。在程序里,你需要一些堆栈。这个堆栈的元素可能是整数、浮点或者别的什么类型。一份整型堆栈的代码可能是: + +``` C++ +class StackInt +{ +public: + void push(Int v); + Int pop(); + Int Find(Int x) + { + for(Int i = 1; i <= size; ) + { + if(data[i] == x) { return i; } + } + } + // ... 其他代码 ... +}; +``` + +如果你要支持浮点了,那么你只能将代码再次拷贝出来,并作如下修改: + +``` C++ +class StackFloat +{ +public: + void push(Float v); + Float pop(); + Int Find(Float x) + { + for(Int i = 1; i <= size; ) + { + if(data[i] == x) { return i; } + } + } + // ... 其他代码 ... +}; +``` + +当然也许你觉得这样做能充分体会代码行数增长的成就感。但是有一天,你突然发现:呀,`Find` 函数实现有问题了。怎么办?这个时候也许你只有两份这样的代码,那好说,一一去修正就好了。如果你有十个呢?二十个?五十个? + +时间一长,你就厌倦了这样的生活。你觉得每个堆栈都差不多,但是又有点不一样。为了这一点点不一样,你付出了太多的时间。吃饭的时间,泡妞的时间,睡觉的时间,看岛国小电影顺便练习小臂力量的时间。 + +于是便诞生了新的技术,来消解我们的烦恼。 + +这个技术的名字,并不叫“模板”,而是叫“元编程”。 + ###2.2 模板世界的If-Then-Else:类模板的特化与偏特化 ###2.3 函数模板的重载、参数匹配、特化与部分特化 ###2.4 技巧单元:模板与继承 From a86fa7ee324e622c47c749d0f1b5387a9d69ee0c Mon Sep 17 00:00:00 2001 From: Ye WU Date: Sat, 1 Mar 2014 00:06:21 -0800 Subject: [PATCH 12/99] =?UTF-8?q?2.1=E8=8A=82=E5=86=99=E4=BD=9C=E7=BB=93?= =?UTF-8?q?=E6=9D=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index ea62313..9bfe836 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -579,6 +579,87 @@ public: 这个技术的名字,并不叫“模板”,而是叫“元编程”。 +元(meta)无论在中文还是英文里,都是个很“抽象(abstract)”的词。因为它的本意就是“抽象”。元编程,也可以说就是“编程的抽象”。用更好理解的说法,元编程意味着你撰写一段程序A,程序A会运行后生成另外一个程序B,程序B才是真正实现功能的程序。那么这个时候程序A可以称作程序B的元程序,撰写程序A的过程,就称之为“元编程”。 + +回到我们的堆栈的例子。真正执行功能的,其实仍然是浮点的堆栈、整数的堆栈、各种你所需要的类型的堆栈。但是因为这些堆栈之间太相似了,仅仅有着些微的不同,我们为什么不能有一个将相似之处囊括起来,同时又能分别体现出不同之处的程序呢?很多语言都提供了这样的机会。C中的宏,C++中的模板,Python中的Duck Typing,广义上将都能够实现我们的思路。 + +我们的目的,是找出程序之间的相似性,进行“元编程”。而在C++中,元编程的手段,可以是宏,也可以是模板。 + +宏的例子姑且不论,我们来看一看模板: + +``` C++ +template +class Stack +{ +public: + void push(T v); + T pop(); + Int Find(T x) + { + for(Int i = 0; i <= size; ++i) + { + if(data[i] == x) { return i; } + } + } + // ... 其他代码 ... +}; + +typedef Stack StackInt; +typedef Stack StackFloat; +``` + +通过模板,我们可以将形形色色的堆栈代码分为两个部分,一个部分是不变的接口,以及近乎相同的实现;另外一部分是元素的类型,它们是需要变化的。因此同函数类似,需要变化的部分,由模板参数来反应;不变的部分,则是模板内的代码。可以看到,使用模板的代码,要比不使用模板的代码简洁许多。 + +如果元编程中所有的变化的量(或者说元编程的参数),都是类型,那么这样的编程,我们有个特定的称呼,叫“泛型”。 + +但是你会问,模板的发明,仅仅是为了做和宏几乎一样的替换工作吗?可以说是,也可以说不是。一方面,很多时候模板就是为了替换类型,这个时候作用上其实和宏没什么区别。只是宏是基于文本的替换,被替换的文本本身没有任何语义。只有替换完成,编译器才能进行接下来的处理。而模板会在分析模板时以及实例化模板时时候都会进行检查,而且源代码中也能与调试符号一一对应,所以无论是编译时还是运行时,排错都相对简单。 + +但是模板也和宏有很大的不同,否则此文也就不能成立了。模板最大的不同在于它是“可以运算”的。我们来举一个例子,不过可能有点牵强。考虑我们要写一个向量逐分量乘法。只不过这个向量,它非常的大。所以为了保证速度,我们需要使用SIMD指令进行加速。假设我们有以下指令可以使用: + +``` +Int8,16: N/A +Int32 : VInt32Mul(int32 * 4, int32 * 4) +Int64 : VInt64Mul(int64 * 2, int64 * 2) +Float : VInt64Mul(float * 2, float * 2) +``` +所以对于Int8和Int16,我们需要提升到Int32,而Int32和Int64,各自使用自己的指令。所以我们需要实现下的逻辑: + +``` C++ +for(v4a, v4b : vectorsA, vectorsB) +{ + if type is Int8, Int16 + VInt32Mul( ConvertToInt32(v4a), ConvertToInt32(v4b) ) + elif type is Int32 + VInt32Mul( v4a, v4b ) + elif type is Float + ... +} +``` + +这里的问题就在于,如何根据 `type` 分别提供我们需要的实现?这里有两个难点。首先,if type == xxx 是不存在于C++中的。第二,即便存在根据 `type` 的分配方法,我们也不希望它在运行时branch,这样会变得很慢。我们希望它能按照类型直接就把代码编译好,就跟直接写的一样。 + +嗯,聪明你果然想到了,重载也可以解决这个问题。 + +``` C++ +GenericMul(int8 * 4, int8 * 4); +GenericMul(int16 * 4, int16 * 4); +GenericMul(int32 * 4, int32 * 4); +GenericMul(int64 * 4, int64 * 4); +// 其它 Generic Mul ... + +for(v4a, v4b : vectorsA, vectorsB) +{ + GenericMul(v4a, v4b); +} + +``` + +这样不就可以了吗? + +唔,你赢了,是这样没错。但是问题是,我这个平台是你可没见过,它叫 `Deep Thought`, 特别缺心眼儿,不光有 `int8`,还有更奇怪的 `int9`, `int11`,以及可以代表世间万物的 `int42`。你总不能为之提供所有的重载吧?这简直就像你枚举了所有程序的输入,并为之提供了对应的输出一样。 + +好吧,我承认这个例子还是太牵强了。不过相信我,在你阅读完第二章和第三章之后,你会将这些特性自如地运用到你的程序之中。你的程序将会变成体现模板“可运算”威力的最好例子。 + ###2.2 模板世界的If-Then-Else:类模板的特化与偏特化 ###2.3 函数模板的重载、参数匹配、特化与部分特化 ###2.4 技巧单元:模板与继承 From 5034d9d7b5b3ab23dff1d07c3c7149eb3081b391 Mon Sep 17 00:00:00 2001 From: Ye WU Date: Mon, 3 Mar 2014 00:52:36 -0800 Subject: [PATCH 13/99] =?UTF-8?q?2.2.1=E6=92=B0=E5=86=99=E4=BA=86=E6=A0=B9?= =?UTF-8?q?=E6=8D=AE=E7=B1=BB=E5=9E=8B=E9=80=89=E6=8B=A9=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=9A=84=E4=BE=8B=E5=AD=90=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 9bfe836..5ef4106 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -636,7 +636,7 @@ for(v4a, v4b : vectorsA, vectorsB) } ``` -这里的问题就在于,如何根据 `type` 分别提供我们需要的实现?这里有两个难点。首先,if type == xxx 是不存在于C++中的。第二,即便存在根据 `type` 的分配方法,我们也不希望它在运行时branch,这样会变得很慢。我们希望它能按照类型直接就把代码编译好,就跟直接写的一样。 +这里的问题就在于,如何根据 `type` 分别提供我们需要的实现?这里有两个难点。首先, `if(type == xxx) {}` 是不存在于C++中的。第二,即便存在根据 `type` 的分配方法,我们也不希望它在运行时branch,这样会变得很慢。我们希望它能按照类型直接就把代码编译好,就跟直接写的一样。 嗯,聪明你果然想到了,重载也可以解决这个问题。 @@ -661,6 +661,76 @@ for(v4a, v4b : vectorsA, vectorsB) 好吧,我承认这个例子还是太牵强了。不过相信我,在你阅读完第二章和第三章之后,你会将这些特性自如地运用到你的程序之中。你的程序将会变成体现模板“可运算”威力的最好例子。 ###2.2 模板世界的If-Then-Else:类模板的特化与偏特化 + +####2.2.1 根据类型执行代码 +前一节给出了一个例子,从例子中可以看出,有时候我们需要做出根据类型执行不同代码的事情。要达成这一目的,模板并不是唯一的途径。比如之前我们所说的重载。如果把眼界放宽一些,虚函数也是根据类型执行代码的例子。此外,在C语言时代,也会有一些技法来达到这个目的,比如下面这个例子,我们需要对两个浮点做加法, 或者对两个整数做乘法: + +``` C +struct Variant +{ + union + { + int x; + float y; + } data; + uint32 typeId; +}; + +Variant addFloatOrMulInt(Variant const* a, Variant const* b) +{ + Variant ret; + assert(a->typeId == b->typeId); + if (a->typeId == TYPE_INT) + { + ret.x = a->x * b->x; + } + else + { + ret.y = a->y + b->y; + } + return ret; +} + +``` + +更常见的是 `void*`: + +``` C++ +#define BIN_OP(type, a, op, b, result) (*(type const *)(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) + { + BIN_OP(int, data0, *, data1, out); + } + else + { + BIN_OP(float, data0, +, data1, out); + } +} +``` + +在C++中比如在 `Boost.Any` 的实现中,运用了 `typeid` 来查询类型信息。和 `typeid` 同属于RTTI机制的 `dynamic_cast`,也经常会用来做类型判别的工作。我想你应该写过类似于下面的代码 + +``` C++ +IAnimal* animal = GetAnimalFromSystem(); + +IDog* maybeDog = dynamic_cast(animal); +if(maybeDog) +{ + maybeDog->Wangwang(); +} +ICat* maybeCat = dynamic_cast(animal); +if(maybeCat) +{ + maybeCat->Moemoe(); +} +``` + +当然,在实际的工作中,我们建议把需要 `dynamic_cast` 后执行的代码,尽量变成虚函数。不过这个已经是另外一个问题了。我们看到,不管是哪种方法都很难避免 `if` 的存在。而且因为输入数据的类型是模糊的,经常需要强制地、没有任何检查的转换成某个类型,因此很容易出错。 + +模板与这些方法有区别吗? + ###2.3 函数模板的重载、参数匹配、特化与部分特化 ###2.4 技巧单元:模板与继承 From 97fc92526cb7e7857d133e94a368a9ab19ea2729 Mon Sep 17 00:00:00 2001 From: Ye WU Date: Mon, 3 Mar 2014 00:57:28 -0800 Subject: [PATCH 14/99] =?UTF-8?q?2.2.1=E8=A1=8C=E6=96=87=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 5ef4106..7de3ec8 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -663,7 +663,7 @@ for(v4a, v4b : vectorsA, vectorsB) ###2.2 模板世界的If-Then-Else:类模板的特化与偏特化 ####2.2.1 根据类型执行代码 -前一节给出了一个例子,从例子中可以看出,有时候我们需要做出根据类型执行不同代码的事情。要达成这一目的,模板并不是唯一的途径。比如之前我们所说的重载。如果把眼界放宽一些,虚函数也是根据类型执行代码的例子。此外,在C语言时代,也会有一些技法来达到这个目的,比如下面这个例子,我们需要对两个浮点做加法, 或者对两个整数做乘法: +前一节的示例提出了一个要求:需要做出根据类型执行不同代码。要达成这一目的,模板并不是唯一的途径。比如之前我们所说的重载。如果把眼界放宽一些,虚函数也是根据类型执行代码的例子。此外,在C语言时代,也会有一些技法来达到这个目的,比如下面这个例子,我们需要对两个浮点做加法, 或者对两个整数做乘法: ``` C struct Variant From 761998bcedc40ea06801194d3c76f3f460329b5d Mon Sep 17 00:00:00 2001 From: Ye WU Date: Mon, 3 Mar 2014 22:57:28 -0800 Subject: [PATCH 15/99] =?UTF-8?q?2.2.1=20=E7=BB=93=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 7de3ec8..942cfba 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -710,7 +710,7 @@ void doDiv(void* out, void const* data0, void const* data1, DATA_TYPE type) } ``` -在C++中比如在 `Boost.Any` 的实现中,运用了 `typeid` 来查询类型信息。和 `typeid` 同属于RTTI机制的 `dynamic_cast`,也经常会用来做类型判别的工作。我想你应该写过类似于下面的代码 +在C++中比如在 `Boost.Any` 的实现中,运用了 `typeid` 来查询类型信息。和 `typeid` 同属于RTTI机制的 `dynamic_cast`,也经常会用来做类型判别的工作。我想你应该写过类似于下面的代码: ``` C++ IAnimal* animal = GetAnimalFromSystem(); @@ -729,7 +729,48 @@ if(maybeCat) 当然,在实际的工作中,我们建议把需要 `dynamic_cast` 后执行的代码,尽量变成虚函数。不过这个已经是另外一个问题了。我们看到,不管是哪种方法都很难避免 `if` 的存在。而且因为输入数据的类型是模糊的,经常需要强制地、没有任何检查的转换成某个类型,因此很容易出错。 -模板与这些方法有区别吗? +但是模板与这些方法最大的区别并不在这里。模板无论其参数或者是类型,它都是一个编译期分派的办法。编译期就能确定的东西既可以做类型检查,编译器也能进行优化,砍掉任何不必要的代码执行路径。例如在上例中, + +``` C++ +template T addFloatOrMulInt(T a, T b); + +// 迷之代码1:用于T是float的情况 + +// 迷之代码2:用于T是int时的情况 +``` + +如果你运用了模板来实现,那么当传入两个不同类型的变量,或者不是 `int` 和 `float` 变量,编译器就会提示错误。但是如果使用了我们前述的 `Variant` 来实现,编译器可就管不了那么多了。但是,成也编译期,败也编译期。最严重的“缺点”,就是你没办法根据用户输入或者别的什么在运行期间可能发生变化的量来决定它产生、或执行什么代码。比如下面的代码段,它是不成立的。 + +``` C++ + +template +int foo() { return i + j; } +int main() +{ + cin >> x >> y; + return foo(); +} + +``` + +这点限制也粉碎了妄图用模板来包办工厂(Factory)甚至是反射的梦想。尽管在《Modern C++ Design》中(别问我为什么老举这本书,因为《C++ Templates》和《Generic Programming》我只是囫囵吞枣读过,基本不记得了)大量运用模板来简化工厂方法;同时C++11和14中的一些机制如Vardric Template更是让这一问题的解决更加彻底。但无论如何,光靠模板你就是写不出依靠类名或者ID变量产生类型实例的代码。 + +所以说,从能力上来看,模板能做的事情都是编译期完成的。编译期完成的意思就是,当你编译一个程序的时候,所有的量就都已经确定了。比如下面的这个例子: + +``` C++ +int a = 3, b = 5; +Variant aVar, bVar; +aVar.setInt(a); // 我们新加上的方法,怎么实现的无所谓,大家明白意思就行了。 +bVar.setInt(b); +Variant result = addFloatOrMulInt(aVar, bVar); +``` + +除非世界末日,否则这个例子里不管你怎么蹦跶,单看代码我们就能知道, `aVar` 和 `bVar` 都一定会是整数。所以如果有合适的机制,编译器就能知道此处的 `addFloatOrMulInt` 中只需要执行 `Int` 路径上的代码,而且编译器在此处也能单独为 `Int` 路径生成代码,从而去掉那个不必要的 `if`。 + +在模板代码中,这个“合适的机制”就是指“特化”和“部分特化(Partial Specialization)”,后者也叫“偏特化”。 + +####2.2.2模板原型与偏特化 + ###2.3 函数模板的重载、参数匹配、特化与部分特化 ###2.4 技巧单元:模板与继承 From 994361be00ed70df925e4839e854622cef03cbcc Mon Sep 17 00:00:00 2001 From: Ye WU Date: Wed, 5 Mar 2014 22:19:01 -0800 Subject: [PATCH 16/99] =?UTF-8?q?2.2.2=E8=8A=82=E7=BB=93=E6=9D=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CppTemplateTutorial.cpp | 41 +++++++- ReadMe.md | 216 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 255 insertions(+), 2 deletions(-) diff --git a/CppTemplateTutorial.cpp b/CppTemplateTutorial.cpp index 55275a9..2f26955 100644 --- a/CppTemplateTutorial.cpp +++ b/CppTemplateTutorial.cpp @@ -1,4 +1,4 @@ -#include "stdafx.h" +#include "stdafx.h" #include #include @@ -151,6 +151,45 @@ namespace _1_3 #endif } +namespace _2_2_2 +{ + template class AddFloatOrMulInt + { + static T Do(T a, T b) + { + // 在这个例子里面一般形式里面是什么内容不重要,因为用不上 + // 这里就随便给个0吧。 + return T(0); + } + }; + + // 其次,我们要指定T是int时候的代码,这就是特化: + template <> class AddFloatOrMulInt + { + public: + static int Do(int a, int b) + { + return a * b; + } + }; + + // 再次,我们要指定T是float时候的代码: + template <> class AddFloatOrMulInt + { + public: + static float Do(float a, float b) + { + return a * b; + } + }; + + void foo() + { + float a(0), b(1); + float c = AddFloatOrMulInt::Do(a, b); + } +} + // 1.4 Specialization, Partial Specialization, Full Specialization namespace _1_4 { diff --git a/ReadMe.md b/ReadMe.md index 942cfba..dce615a 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -769,8 +769,222 @@ Variant result = addFloatOrMulInt(aVar, bVar); 在模板代码中,这个“合适的机制”就是指“特化”和“部分特化(Partial Specialization)”,后者也叫“偏特化”。 -####2.2.2模板原型与偏特化 +####2.2.2特化 +我的高中物理老师对我说过一句令我受用至今的话:把自己能做的事情做好。编写模板程序也是一样。当你试图用模板解决问题之前,先撇开那些复杂的语法要素,用最直观的方式表达你的需求: + +``` C++ +// 这里是伪代码,意思一下 + +int|float addFloatOrMulInt(a, b) +{ + if(type is Int) + { + return a * b; + } + else if (type is Float) + { + return a + b; + } +} + +void foo() +{ + float a, b, c; + c = addFloatOrMulInt(a, b); // c = a + b; + + int x, y, z; + z = addFloatOrMulInt(x, y); // z = x * y; +} +``` + +因为这一节是讲类模板有关的特化和偏特化机制,所以我们不用普通的函数,而是用类的静态成员函数来做这个事情(这就是典型的没事找抽型): + +``` C++ +// 这里仍然是伪代码,意思一下,too。 +class AddFloatOrMulInt +{ + static int|float Do(a, b) + { + if(type is Int) + { + return a * b; + } + else if (type is Float) + { + return a + b; + } + } +}; + +void foo() +{ + float a, b, c; + c = AddFloatOrMulInt::Do(a, b); // c = a + b; + + int x, y, z; + z = AddFloatOrMulInt::Do(x, y); // z = x * y; +} +``` + +好,意思表达清楚了。我们先从调用方的角度,把这个形式改写一下: + +``` C++ +void foo() +{ + float a, b, c; + c = AddFloatOrMulInt::Do(a, b); // c = a + b; + + int x, y, z; + z = AddFloatOrMulInt::Do(x, y); // z = x * y; +} +``` +也许你不明白为什么要改写成现在这个样子。看不懂不怪你,怪我讲的不好。但是你别急,先看看这样改写以后能不能跟我们的目标接近一点。如果我们把 `AddFloatOrMulInt::Do` 看作一个普通的函数,那么我们可以写两个实现出来: + +``` C++ +float AddFloatOrMulInt::Do(float a, float b) +{ + return a + b; +} + +int AddFloatOrMulInt::Do(int a, int b) +{ + return a * b; +} + +void foo() +{ + float a, b, c; + c = AddFloatOrMulInt::Do(a, b); // c = a + b; + + int x, y, z; + z = AddFloatOrMulInt::Do(x, y); // z = x * y; +} +``` + +这样是不是就很开心了?我们更进一步,把 `AddFloatOrMulInt::Do` 换成合法的类模板: + +``` C++ +// 这个是给float用的。 +template class AddFloatOrMulInt +{ + T Do(T a, T b) + { + return a + b; + } +}; + +// 这个是给int用的。 +template class AddFloatOrMulInt +{ + T Do(T a, T b) + { + return a * b; + } +}; + +void foo() +{ + float a, b, c; + + // 嗯,我们需要 c = a + b; + c = AddFloatOrMulInt::Do(a, b); + // ... 觉得哪里不对劲 ... + // ... + // ... + // ... + // 啊!有两个AddFloatOrMulInt,class看起来一模一样,要怎么区分呢! +} +``` +好吧,问题来了!如何要让两个内容不同,但是模板参数形式相同的类进行区分呢?特化!特化(specialization)是根据一个或多个特殊的整数或类型,给出模板实例化时的一个指定内容。我们先来看特化是怎么应用到这个问题上的。 +``` C++ +// 首先,要写出模板的一般形式(原型) +template class AddFloatOrMulInt +{ + static T Do(T a, T b) + { + // 在这个例子里面一般形式里面是什么内容不重要,因为用不上 + // 这里就随便给个0吧。 + return T(0); + } +}; + +// 其次,我们要指定T是int时候的代码,这就是特化: +template <> class AddFloatOrMulInt +{ +public: + static int Do(int a, int b) // + { + return a * b; + } +}; + +// 再次,我们要指定T是float时候的代码: +template <> class AddFloatOrMulInt +{ +public: + static float Do(float a, float b) + { + return a + b; + } +}; + +void foo() +{ + // 这里面就不写了 +} +``` +我们再把特化的形式拿出来一瞧:这货有点怪啊: `template <> class AddFloatOrMulInt`。别急,我给你解释一下。 + +``` C++ +// 我们这个模板的基本形式是什么? +template class AddFloatOrMulInt; + +// 但是这个类,是给T是Int的时候用的,于是我们写作 +class AddFloatOrMulInt +// 当然,这里编译是通不过的。 + +// 但是它又不是个普通类,而是类模板的一个特化(特例)。 +// 所以前面要加模板关键字template, +// 以及模板参数列表 +template class AddFloatOrMulInt; + +// 最后,模板参数列表里面填什么?因为原型的T已经被int取代了。所以这里就不能放任何额外的参数了。 +// 所以这里要放空。 +template <> class AddFloatOrMulInt +{ + // ... 针对Int的实现 ... +} + +// Bingo! +``` + +哈,这样就好了。我们来做一个练习。我们有一些类型,然后你要用模板做一个对照表,让类型对应上一个数字。我先来做一个示范: + +``` C++ + +template TypeToID +{ + static int const ID = -1; +}; + +template <> TypeToID +{ + static int const ID = 0; +}; +``` + +然后呢,你的任务就是,要所有无符号的整数类型的特化(其实就是`uint8_t`到`uint64_t`啦),把所有的基本类型都赋予一个ID(当然是不一样的啦)。当你做完后呢,可以把类型所对应的ID打印出来,我仍然以 `uint8_t` 为例: + +``` C++ +void PrintID() +{ + cout << "ID of uint8_t: " << TypeToID::ID << endl; +} +``` +嗯,看起来挺简单的,是吧。但是这里透露出了一个非常重要的信号,我希望你已经能察觉出来了: `TypeToID` 如同是一个函数。这个函数只能在编译期间执行。它输入一个类型,输出一个ID。 + +如果你体味到了这一点,那么恭喜你,你的模板元编程已经开悟了。 ###2.3 函数模板的重载、参数匹配、特化与部分特化 ###2.4 技巧单元:模板与继承 From 48610a1a97dc68a26de5609a8e97d2d261639665 Mon Sep 17 00:00:00 2001 From: Ye WU Date: Thu, 20 Mar 2014 23:43:01 -0700 Subject: [PATCH 17/99] =?UTF-8?q?2.2.3=20=E8=8A=82=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=86=85=E5=AE=B9=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CppTemplateTutorial.cpp | 17 +++++++++++ ReadMe.md | 63 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/CppTemplateTutorial.cpp b/CppTemplateTutorial.cpp index 2f26955..710f95c 100644 --- a/CppTemplateTutorial.cpp +++ b/CppTemplateTutorial.cpp @@ -190,6 +190,23 @@ namespace _2_2_2 } } +namespace _2_2_3 +{ + template class TypeToID + { + public: + static int const ID = -1; + }; + + class B {}; + + template <> class TypeToID; // 函数的TypeID + template <> class TypeToID; // 数组的TypeID + template <> class TypeToID; // 这是以数组为参数的函数的TypeID + template <> class TypeToID; // 我也不知道这是什么了,自己看着办吧。 + + template <> class TypeToID; +} // 1.4 Specialization, Partial Specialization, Full Specialization namespace _1_4 { diff --git a/ReadMe.md b/ReadMe.md index dce615a..c168f3e 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -769,7 +769,7 @@ Variant result = addFloatOrMulInt(aVar, bVar); 在模板代码中,这个“合适的机制”就是指“特化”和“部分特化(Partial Specialization)”,后者也叫“偏特化”。 -####2.2.2特化 +####2.2.2 特化 我的高中物理老师对我说过一句令我受用至今的话:把自己能做的事情做好。编写模板程序也是一样。当你试图用模板解决问题之前,先撇开那些复杂的语法要素,用最直观的方式表达你的需求: @@ -963,13 +963,15 @@ template <> class AddFloatOrMulInt ``` C++ -template TypeToID +template class TypeToID { +public: static int const ID = -1; }; -template <> TypeToID +template <> class TypeToID { +public: static int const ID = 0; }; ``` @@ -986,6 +988,61 @@ void PrintID() 如果你体味到了这一点,那么恭喜你,你的模板元编程已经开悟了。 +####2.2.3 偏特化 + +在上一节结束之后,你一定做了许多的练习。我们再来做三个练习。第一,给`float`一个ID;第二,给`void*`一个ID;第三,给任意类型的指针一个ID。先来做第一个: + +``` +// ... +// TypeToID 的模板“原型” +// ... + +template class TypeToID +{ + static int const ID = 0xF10A7; +}; +``` + +嗯, 这个你已经了然于心了。那么`void*`呢?你想了想,这已经是一个复合类型了。不错你还是战战兢兢的写了下来: + +``` +template <> class TypeToID +{ + static int const ID = 0x401d; +}; + +void PrintID() +{ + cout << "ID of uint8_t: " << TypeToID::ID << endl; +} +``` + +遍译运行一下,对了。模板不过如此嘛。然后你觉得自己已经完全掌握了,并试图将所有C++类型都放到模板里面,开始了自我折磨的过程: + +``` +class ClassB {}; + +template <> class TypeToID; // 函数的TypeID +template <> class TypeToID; // 数组的TypeID +template <> class TypeToID; // 这是以数组为参数的函数的TypeID +template <> class TypeToID< + int (ClassB::*[3])(void*, float[2])>; // 我也不知道这是什么了,自己看着办吧。 +``` + +甚至连 `const` 和 `volatile` 都能装进去 + +``` +template <> class TypeToID; +``` + +此时就很明白了,只要 `<>` 内填进去的是一个C++能解析的合法类型,模板都能让你特化。不过这个时候如果你一点都没有写错的话, `PrintID` 中只打印了我们提供了特化的类型的ID。那如果我们没有为之提供特化的类型呢?比如说double?OK,实践出真知,我们来尝试着运行一下: + +``` + +``` + +前面的例子里面,我们使用了单参数的模板。不过既然模板有多个参数的形式,那特化也得支持多个参数。 + ###2.3 函数模板的重载、参数匹配、特化与部分特化 ###2.4 技巧单元:模板与继承 From 6ba46efbf146bd2c288f9f4ca0a07cc636c6de88 Mon Sep 17 00:00:00 2001 From: Ye WU Date: Fri, 21 Mar 2014 00:05:40 -0700 Subject: [PATCH 18/99] =?UTF-8?q?=E6=9B=B4=E6=96=B02.2.3=E8=8A=82=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index c168f3e..c6bc8f1 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1037,10 +1037,40 @@ template <> class TypeToID; 此时就很明白了,只要 `<>` 内填进去的是一个C++能解析的合法类型,模板都能让你特化。不过这个时候如果你一点都没有写错的话, `PrintID` 中只打印了我们提供了特化的类型的ID。那如果我们没有为之提供特化的类型呢?比如说double?OK,实践出真知,我们来尝试着运行一下: +``` C++ +void PrintID() +{ + cout << "ID of double: " << TypeToID::ID << endl; +} ``` +嗯,它输出的是-1。我们顺藤摸瓜会看到, `TypeToID`的类模板“原型”的ID是值就是-1。通过这个例子可以知道,当模板实例化时提供的模板参数不能匹配到任何的特化形式的时候,它就会去匹配类模板的“原型”形式。 + +不过这里有一个问题要厘清一下。和继承不同,类模板的“原型”和它的特化类在实现上是没有关系的,并不是在类模板中写了 `ID` 这个Member,那所有的特化就必须要加入 `ID` 这个Member,或者特化就自动有了这个成员。完全没这回事。我们把类模板改成以下形式,或许能看的更清楚一点: + +``` C++ +template class TypeToID +{ +public: + static int const NotID = -2; +}; + +template <> class TypeToID +{ +public: + static int const ID = 1; +}; + +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这个成员。 +} ``` +这样就明白了。类模板和类模板的特化的作用,仅仅是指导编译器选择哪个编译,但是特化之间、特化和它原型的类模板之间,是分别独立实现的。所以如果多个特化、或者特化和对应的类模板有着类似的内容,很不好意思,你得写上若干遍了。 + 前面的例子里面,我们使用了单参数的模板。不过既然模板有多个参数的形式,那特化也得支持多个参数。 ###2.3 函数模板的重载、参数匹配、特化与部分特化 From a1a088a4d8ca05aaf595f68d59baa13bf782255c Mon Sep 17 00:00:00 2001 From: Ye WU Date: Fri, 21 Mar 2014 00:06:58 -0700 Subject: [PATCH 19/99] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E5=87=A0?= =?UTF-8?q?=E5=A4=84=E4=BB=A3=E7=A0=81=E7=9D=80=E8=89=B2=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index c6bc8f1..52d047e 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -992,7 +992,7 @@ void PrintID() 在上一节结束之后,你一定做了许多的练习。我们再来做三个练习。第一,给`float`一个ID;第二,给`void*`一个ID;第三,给任意类型的指针一个ID。先来做第一个: -``` +``` C++ // ... // TypeToID 的模板“原型” // ... @@ -1005,7 +1005,7 @@ template class TypeToID 嗯, 这个你已经了然于心了。那么`void*`呢?你想了想,这已经是一个复合类型了。不错你还是战战兢兢的写了下来: -``` +``` C++ template <> class TypeToID { static int const ID = 0x401d; @@ -1019,7 +1019,7 @@ void PrintID() 遍译运行一下,对了。模板不过如此嘛。然后你觉得自己已经完全掌握了,并试图将所有C++类型都放到模板里面,开始了自我折磨的过程: -``` +``` C++ class ClassB {}; template <> class TypeToID; // 函数的TypeID @@ -1031,7 +1031,7 @@ template <> class TypeToID< 甚至连 `const` 和 `volatile` 都能装进去 -``` +``` C++ template <> class TypeToID; ``` From 210399e217028586f39d83c7b07e47423f97e768 Mon Sep 17 00:00:00 2001 From: Ye WU Date: Fri, 21 Mar 2014 00:10:29 -0700 Subject: [PATCH 20/99] =?UTF-8?q?2.2.3=E6=A0=87=E9=A2=98=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 52d047e..1b25460 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -988,7 +988,7 @@ void PrintID() 如果你体味到了这一点,那么恭喜你,你的模板元编程已经开悟了。 -####2.2.3 偏特化 +####2.2.3 特化:一些其它问题 在上一节结束之后,你一定做了许多的练习。我们再来做三个练习。第一,给`float`一个ID;第二,给`void*`一个ID;第三,给任意类型的指针一个ID。先来做第一个: From d9f76cd8b12f52588b4e85e590b44797a7b9cbc5 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 8 Apr 2014 16:42:26 -0700 Subject: [PATCH 21/99] =?UTF-8?q?2.2.3=E8=8A=82=E7=BB=93=E6=9D=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 1b25460..a776f61 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1071,7 +1071,169 @@ void PrintID() 这样就明白了。类模板和类模板的特化的作用,仅仅是指导编译器选择哪个编译,但是特化之间、特化和它原型的类模板之间,是分别独立实现的。所以如果多个特化、或者特化和对应的类模板有着类似的内容,很不好意思,你得写上若干遍了。 -前面的例子里面,我们使用了单参数的模板。不过既然模板有多个参数的形式,那特化也得支持多个参数。 +第三个问题,是写一个模板匹配任意类型的指针。对于C语言来说,因为没有泛型的概念,因此它提供了无类型的指针`void*`。它的优点是,所有指针都能转换成它。它的缺点是,一旦转换称它后,你就再也不知道这个指针到底是指向`float`或者是`int`或者是`struct`了。 + +比如说`copy`。 + +``` C +void copy(void* dst, void const* src, size_t elemSize, size_t elemCount, void (*copyElem)(void* dstElem, void const* srcElem)) +{ + void const* reader = src; + void const* writer = dst; + for(size_t i = 0; i < elemCount; ++i) + { + copyElem(writer, reader); + advancePointer(reader, elemSize); // 把Reader指针往后移动一些字节 + advancePointer(writer, elemSize); + } +} +``` + +为什么要提供copyElem,是因为可能有些struct需要深拷贝,所以得用特殊的copy函数。这个在C++98/03里面就体现为拷贝构造和赋值函数。 +但是不管怎么搞,因为这个函数的参数只是`void*`而已,当你使用了错误的elemSize,或者传入了错误的copyElem,就必须要到运行的时候才有可能看出来。注意,这还只是有可能而已。 + + +那么C++有了模板后,能否既能匹配任意类型的指针,同时又保留了类型信息呢?答案是显然的。至于怎么写,那就得充分发挥你的直觉了: + +首先,我们需要一个`typename T`来指代“任意类型”这四个字: + +``` C++ +template +``` + +接下来,我们要写函数原型: + +``` C++ +void copy(?? dest, ?? src, size_t elemCount); +``` + +这里的 `??` 要怎么写呢?既然我们有了模板类型参数T,那我们不如就按照经验,写 `T*` 看看。 + +``` C++ +template +void copy(T* dst, T const* src, size_t elemCount); +``` + +编译一下,咦,居然通过了。看来这里的语法与我们以前学到的知识并没有什么不同。这也是语言设计最重要的一点原则:一致性。它可以让你辛辛苦苦体验到的规律不至于白费。 +最后就是实现: + +``` C++ +template +void copy(T* dst, T const* src, size_t elemCount) +{ + for(size_t i = 0; i < elemCount; ++i) + { + dst[i] = src[i]; + } +} +``` + +是不是简洁了许多?你不需要再传入size;只要你有正确的赋值函数,也不需要提供定制的copy;也不用担心dst和src的类型不匹配了。 + +最后,我们把函数模板学到的东西,也应用到类模板里面: + +``` C++ +template // 嗯,需要一个T +class TypeToID // 我要对所有的指针类型特化,所以这里就写T* +{ +public: + static int const ID = 0x80000000; // 用最高位表示它是一个指针 +}; +``` + +最后写个例子来测试一下,看看我们的 `T*` 能不能搞定 `float*` + +``` C++ +void PrintID() +{ + cout << "ID of float*: " << TypeToID::ID << endl; +} +``` + +哈哈,大功告成。嗯,别急着高兴。待我问一个问题:你知道 `TypeToID` 后,这里的T是什么吗?换句话说,你知道下面这段代码打印的是什么吗? + +``` C++ +// ... +// TypeToID 的其他代码,略过不表 +// ... + +template // 嗯,需要一个T +class TypeToID // 我要对所有的指针类型特化,所以这里就写T* +{ +public: + typedef T SameAsT; + static int const ID = 0x80000000; // 用最高位表示它是一个指针 +}; + +void PrintID() +{ + cout << "ID of float*: " << TypeToID< TypeToID::SameAsT >::ID << endl; +} +``` + +别急着运行,你先猜。 + +------------------------- 这里是给勤于思考的码猴的分割线 ------------------------------- + +OK,猜出来了吗,T是`float`。为什么呢?因为你用 `float *` 匹配了 `T *`,所以 `T` 就对应 `float` 了。没想清楚的自己再多体会一下。 + +嗯,所以实际上,我们可以利用这个特性做一件事情:把指针类型的那个指针给“干掉”: + +``` C++ +template +class RemovePointer +{ + // 啥都不干,你要放一个不是指针的类型进来,我就让你死的难看。 +}; + +template +class RemovePointer // 祖传牛皮藓,专治各类指针 +{ +public: + typedef T Result; +}; + +void Foo() +{ + RemovePointer::Result x = 5.0f; // 喏,用RemovePointer后,那个Result就是把float*的指针处理掉以后的结果:float啦。 + std::cout << x << std::endl; +} +``` + +OK,如果这个时候,我需要给 `int*` 提供一个更加特殊的特化,那么我还得都多提供一个: + +``` C++ +// ... +// TypeToID 的其他代码,略过不表 +// ... + +template // 嗯,需要一个T +class TypeToID // 我要对所有的指针类型特化,所以这里就写T* +{ +public: + typedef T SameAsT; + static int const ID = 0x80000000; // 用最高位表示它是一个指针 +}; + +template <> // 嗯,int* 已经是个具体的不能再具体的类型了,所以模板不需要额外的类型参数了 +class TypeToID // 嗯,对int*的特化。在这里呢,要把int*整体看作一个类型。 +{ +public: + static int const ID = 0x12345678; // 给一个缺心眼的ID +}; + +void PrintID() +{ + cout << "ID of int*: " << TypeToID::ID << endl; +} +``` + +嗯,这个时候它会输出0x12345678的十进制(大概?)。 +可能会有较真的人说,`int*` 去匹配 `T` 或者 `T*`,也是合法的。就和你说22岁以上能结婚,那24岁当然也能结婚一样。 +那为什么 `int*` 就会找 `int*`,`float *`因为没有合适的特化就去找 `T*`,更一般的就去找 `T` 呢?废话,有专门为你准备的东西的不用,人干事?这就是直觉。 +但是呢,直觉对付更加复杂的问题还是没用的(也不是没用,主要是你没这个直觉了)。我们要把这个直觉,转换成合理的规则————即模板的匹配规则。 +当然,这个匹配规则是对复杂问题用的,所以我们会到实在一眼看不出来的时候才会动用它。一开始我们只要把握:匹配手机从最特殊到最一般的原则就可以了。 + ###2.3 函数模板的重载、参数匹配、特化与部分特化 ###2.4 技巧单元:模板与继承 From 45b3a4ea8bfda944cc1a43ca47102df58acd572c Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 8 Apr 2014 16:52:27 -0700 Subject: [PATCH 22/99] =?UTF-8?q?=E4=BF=AE=E6=AD=A32.2.3=E9=83=A8=E5=88=86?= =?UTF-8?q?=E9=94=99=E5=88=AB=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index a776f61..911a06f 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1231,8 +1231,8 @@ void PrintID() 嗯,这个时候它会输出0x12345678的十进制(大概?)。 可能会有较真的人说,`int*` 去匹配 `T` 或者 `T*`,也是合法的。就和你说22岁以上能结婚,那24岁当然也能结婚一样。 那为什么 `int*` 就会找 `int*`,`float *`因为没有合适的特化就去找 `T*`,更一般的就去找 `T` 呢?废话,有专门为你准备的东西的不用,人干事?这就是直觉。 -但是呢,直觉对付更加复杂的问题还是没用的(也不是没用,主要是你没这个直觉了)。我们要把这个直觉,转换成合理的规则————即模板的匹配规则。 -当然,这个匹配规则是对复杂问题用的,所以我们会到实在一眼看不出来的时候才会动用它。一开始我们只要把握:匹配手机从最特殊到最一般的原则就可以了。 +但是呢,直觉对付更加复杂的问题还是没用的(也不是没用,主要是你没这个直觉了)。我们要把这个直觉,转换成合理的规则——即模板的匹配规则。 +当然,这个匹配规则是对复杂问题用的,所以我们会到实在一眼看不出来的时候才会动用它。一开始我们只要把握:模板是从最特殊到最一般形式进行匹配就可以了。 ###2.3 函数模板的重载、参数匹配、特化与部分特化 From b4099d85e175c6db8cc4ebccf15cf92a4ca8385c Mon Sep 17 00:00:00 2001 From: Ye WU Date: Sat, 17 May 2014 13:48:34 -0700 Subject: [PATCH 23/99] fixed issue #7 fixed issue #6 --- ReadMe.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 911a06f..40c425c 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -9,7 +9,7 @@ C++似乎从他为世人所知的那天开始便成为天然的话题性编程 C++之所以变成一门层次丰富、结构多变、语法繁冗的语言,是有着多层次的原因的。Bjarne在《The Design and Evolution of C++》一书中,详细的解释了C++为什么会变成如今(C++98/03)的模样。这本书也是我和陈梓瀚一直对各位已经入门的新手强烈推荐的一本书。通过它你多少可以明白,C++的诸多语法要素之所以变成如今的模样,实属迫不得已。 -模板作为C++中最有特色的语言特性,它堪称玄学的语法和语义,理所应当的成为初学者的梦魇。甚至很多工作多年的人也对C++的模板部分保有充分的敬畏。在多数的编码标准中,Template俨然和多重继承一样,成为了一般程序员(非程序库撰写者)的禁区。甚至运用模板较多的Boost的,也成为了“众矢之的”。 +模板作为C++中最有特色的语言特性,它堪称玄学的语法和语义,理所应当的成为初学者的梦魇。甚至很多工作多年的人也对C++的模板部分保有充分的敬畏。在多数的编码标准中,Template俨然和多重继承一样,成为了一般程序员(非程序库撰写者)的禁区。甚至运用模板较多的Boost,也成为了“众矢之的”。 但是实际上C++模板远没有想象的那么复杂。我们只需要换一个视角:在C++03的时候,模板本身就可以独立成为一门“语言”。它有“值”,有“函数”,有“表达式”和“语句”。除了语法比较蹩脚外,它既没有指针也没有数组,更没有C++里面复杂的继承和多态。可以说,它要比C语言要简单的多。如果我们把模板当做是一门语言来学习,那只需要花费学习OO零头的时间即可掌握。按照这样的思路,可以说在各种模板书籍中出现的多数技巧,都可以被轻松理解。 @@ -405,7 +405,7 @@ error C2783: 'DstT _1_2_2::c_style_cast(SrcT)' : could not deduce template argum 然后你仔细的比较了一下,然后发现 … 模板参数有两个,而参数里面能得到的只有 `SrcT` 一个。结合出错信息看来关键在那个 `DstT` 上。这个时候,你死马当活马医,把模板参数写完整了: ``` C++ -float i = c_style_cast(v); +float i = c_style_cast(v); ``` 嗯,很顺利的通过了。难道C++不能支持让参数推导一部分模板参数吗? From 41eafd2180043b2551248706cefebd32a12caf52 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 9 Mar 2015 17:57:56 -0700 Subject: [PATCH 24/99] Add part code/text for 2.2.4 --- CppTemplateTutorial.cpp | 30 ++++++++++++++++++++++++++++++ ReadMe.md | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/CppTemplateTutorial.cpp b/CppTemplateTutorial.cpp index 710f95c..c836c94 100644 --- a/CppTemplateTutorial.cpp +++ b/CppTemplateTutorial.cpp @@ -207,6 +207,36 @@ namespace _2_2_3 template <> class TypeToID; } + +namespace _2_2_4 +{ + template struct X {}; + + template struct Y + { + typedef X ReboundType; + typedef typename X::MemberType MemberType; +#if WRONG_CODE_ENABLED + typedef WTF MemberType3; +#endif + + static void foo() + { + X instance0; + X::MemberType instan + WTF instance2 + 大王叫我来巡山 - + & + } + }; + + void foo() + { +#if WRONG_CODE_ENABLED + Y::foo(); +#endif + } +} + // 1.4 Specialization, Partial Specialization, Full Specialization namespace _1_4 { diff --git a/ReadMe.md b/ReadMe.md index 911a06f..2a38236 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1234,10 +1234,45 @@ void PrintID() 但是呢,直觉对付更加复杂的问题还是没用的(也不是没用,主要是你没这个直觉了)。我们要把这个直觉,转换成合理的规则——即模板的匹配规则。 当然,这个匹配规则是对复杂问题用的,所以我们会到实在一眼看不出来的时候才会动用它。一开始我们只要把握:模板是从最特殊到最一般形式进行匹配就可以了。 +####2.2.4 即用即推导 + +这一节我们将讲述模板一个非常重要的行为特点:那就是什么时候编译器会对模板进行推导,推导到什么程度。 + +这一知识,对于理解模板的编译期行为、以及修正模板编译错误都非常重要。 + +我们先来看一个例子: + +``` C++ +template struct X {}; + +template struct Y +{ + typedef X ReboundType; // 类型定义1 + typedef typename X::MemberType MemberType; // 类型定义2 + typedef UnknownType MemberType3; // 类型定义3 + + void foo() + { + X instance0; + X::MemberType instance1; + WTF instance2 + 大王叫我来巡山 - + & + } +}; +``` + +把这段代码编译一下,类型定义3出错,其它的都没问题。不过到这里你应该会有几个问题: + +1. 不是`struct X`的定义是空的吗?为什么在`struct Y`内的类型定义2使用了 `X::MemberType` 编译器没有报错? +2. 类型定义2中的`typename`是什么鬼?为什么类型定义1就不需要? +3. 为什么类型定义3会导致编译错误? +4. 为什么`void foo()`在MSVC下什么错误都没报? + ###2.3 函数模板的重载、参数匹配、特化与部分特化 ###2.4 技巧单元:模板与继承 + ## 3 拿起特化的武器,去写程序吧! ###3.1 利用模板特化规则实现If-Then-Else与Switch-Case ###3.2 特化可以有多个选择:替换失败并不是一个错误,只是一种可能 From 473b429e935630b855c29513330ff507709e535c Mon Sep 17 00:00:00 2001 From: Ye WU Date: Tue, 24 Nov 2015 00:33:09 -0800 Subject: [PATCH 25/99] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E2=80=9C=E5=90=8D?= =?UTF-8?q?=E7=A7=B0=E6=9F=A5=E6=89=BE=E2=80=9D=E4=B8=80=E8=8A=82=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 81e22a2..f44ead3 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,4 +1,4 @@ - + # C++ Template 进阶指南 ## 0. 前言 @@ -1234,8 +1234,9 @@ void PrintID() 但是呢,直觉对付更加复杂的问题还是没用的(也不是没用,主要是你没这个直觉了)。我们要把这个直觉,转换成合理的规则——即模板的匹配规则。 当然,这个匹配规则是对复杂问题用的,所以我们会到实在一眼看不出来的时候才会动用它。一开始我们只要把握:模板是从最特殊到最一般形式进行匹配就可以了。 -####2.2.4 即用即推导 +###2.3 即用即推导 +####2.3.1 视若无睹的语法错误 这一节我们将讲述模板一个非常重要的行为特点:那就是什么时候编译器会对模板进行推导,推导到什么程度。 这一知识,对于理解模板的编译期行为、以及修正模板编译错误都非常重要。 @@ -1268,9 +1269,14 @@ template struct Y 3. 为什么类型定义3会导致编译错误? 4. 为什么`void foo()`在MSVC下什么错误都没报? +这时我们就需要请出C++11标准了。这是到目前为止,我们第一次阅读标准。我希望能尽量减少直接阅读标准的次数,因此即便是极为复杂的模板匹配决议我都暂时没有引入标准中的描述。 +然而,Template引入的“双阶段名称查找(Two phase name lookup)”堪称是C++中最黑暗的角落 —— 这是LLVM的团队自己在博客上说的 —— 因此在这里阅读标准是必要的。 + +####2.3.2 名称查找:I am who I am +在C++标准中,名称查找(name lookup)集中出现在三处。第一处是3.4节,标题名就叫“Name Lookup”;第二处在10.2节,继承关系中的名称查找;第三处在14.6节,名称解析(name resolution) -###2.3 函数模板的重载、参数匹配、特化与部分特化 -###2.4 技巧单元:模板与继承 +###2.4 函数模板的重载、参数匹配、特化与部分特化 +###2. 技巧单元:模板与继承 ## 3 拿起特化的武器,去写程序吧! From e02f4f5c0af893cdf83db2f9e6f9959317835764 Mon Sep 17 00:00:00 2001 From: Ye WU Date: Tue, 24 Nov 2015 21:00:51 -0800 Subject: [PATCH 26/99] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E2=80=9C?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E6=9F=A5=E6=89=BE=E2=80=9D=E4=B8=80=E8=8A=82?= =?UTF-8?q?=E3=80=82=20=E6=9B=B4=E6=96=B0=E4=BA=86=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CppTemplateTutorial.cpp | 29 +++- CppTemplateTutorial.vcxproj | 6 +- ReadMe.md | 256 +++++++++++++++++++++++++++++++++++- 3 files changed, 282 insertions(+), 9 deletions(-) diff --git a/CppTemplateTutorial.cpp b/CppTemplateTutorial.cpp index c836c94..f5c0e46 100644 --- a/CppTemplateTutorial.cpp +++ b/CppTemplateTutorial.cpp @@ -215,15 +215,15 @@ namespace _2_2_4 template struct Y { typedef X ReboundType; - typedef typename X::MemberType MemberType; #if WRONG_CODE_ENABLED + typedef typename X::MemberType MemberType; typedef WTF MemberType3; #endif static void foo() { X instance0; - X::MemberType instan + typename X::MemberType instance1; WTF instance2 大王叫我来巡山 - + & } @@ -233,10 +233,35 @@ namespace _2_2_4 { #if WRONG_CODE_ENABLED Y::foo(); + Y::foo(); #endif } } +namespace _2_3_3 { + struct A; + template + struct X + { + void foo(T v) { + A a; + a.v = v; + } + }; + + struct A + { + int v; + }; + + int foo2() + { + X x; + x.foo(5); + return 0; + } +} + // 1.4 Specialization, Partial Specialization, Full Specialization namespace _1_4 { diff --git a/CppTemplateTutorial.vcxproj b/CppTemplateTutorial.vcxproj index 73cd5bc..2e76f8b 100644 --- a/CppTemplateTutorial.vcxproj +++ b/CppTemplateTutorial.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -19,13 +19,13 @@ Application true - v110 + v140 Unicode Application false - v110 + v140 true Unicode diff --git a/ReadMe.md b/ReadMe.md index f44ead3..ce3b07d 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1255,7 +1255,7 @@ template struct Y void foo() { X instance0; - X::MemberType instance1; + typename X::MemberType instance1; WTF instance2 大王叫我来巡山 - + & } @@ -1269,12 +1269,256 @@ template struct Y 3. 为什么类型定义3会导致编译错误? 4. 为什么`void foo()`在MSVC下什么错误都没报? -这时我们就需要请出C++11标准了。这是到目前为止,我们第一次阅读标准。我希望能尽量减少直接阅读标准的次数,因此即便是极为复杂的模板匹配决议我都暂时没有引入标准中的描述。 -然而,Template引入的“双阶段名称查找(Two phase name lookup)”堪称是C++中最黑暗的角落 —— 这是LLVM的团队自己在博客上说的 —— 因此在这里阅读标准是必要的。 +这时我们就需要请出C++11标准 —— 中的某些概念了。这是我们到目前为止第一次参阅标准。我希望能尽量减少直接参阅标准的次数,因此即便是极为复杂的模板匹配决议我都暂时没有引入标准中的描述。 +然而,Template引入的“双阶段名称查找(Two phase name lookup)”堪称是C++中最黑暗的角落 —— 这是LLVM的团队自己在博客上说的 —— 因此在这里,我们还是有必要去了解标准中是如何规定的。 ####2.3.2 名称查找:I am who I am -在C++标准中,名称查找(name lookup)集中出现在三处。第一处是3.4节,标题名就叫“Name Lookup”;第二处在10.2节,继承关系中的名称查找;第三处在14.6节,名称解析(name resolution) +在C++标准中对于“名称查找(name lookup)”这个高大上的名词的诠释,主要集中出现在三处。第一处是3.4节,标题名就叫“Name Lookup”;第二处在10.2节,继承关系中的名称查找;第三处在14.6节,名称解析(name resolution)。 + +名称查找/名称解析,是编译器的基石。对编译原理稍有了解的人,都知道“符号表”的存在即重要意义。考虑一段最基本的C代码: +``` C +int a = 0; +int b; +b = (a + 1) * 2; +printf("Result: %d", b); +``` +在这段代码中,所有出现的符号可以分为以下几类: + +* `int`:类型标识符,代表整型; +* `a`,`b`,`printf`:变量名或函数名; +* `=`,`+`,`*`:运算符; +* `,`,`;`,`(`,`)`:分隔符; + +那么,编译器怎么知道`int`就是整数类型,`b=(a+1)*2`中的`a`和`b`就是整型变量呢?这就是名称查找/名称解析的作用:它告诉编译器,这个标识符(identifer)是在哪里被声明或定义的,它究竟是什么意思。 + +也正因为这个机制非常基础,所以它才会面临各种可能的情况,编译器也要想尽办法让它在大部分场合都表现的合理。比如我们常见的作用域规则,就是为了对付名称在不同代码块中传播、并且遇到重名要如何处理的问题。下面是一个最简单的、大家在语言入门过程中都会碰到的一个例子: +``` C++ +int a = 0; +void f() { + int a = 0; + a += 2; + printf("Inside : %d\n", a); +} +void g() { + printf("Outside : %d\n", a); +} +int main() { + f(); + g(); +} + +/* ------------ Console Output ----------------- +Inside : 2 +Outside : 0 +--------------- Console Output -------------- */ +``` + +我想大家尽管不能处理所有名称查找中所遇到的问题,但是对一些常见的名称查找规则也有了充分的经验,可以解决一些常见的问题。 +但是模板的引入,使得名称查找这一本来就不简单的基本问题变得更加复杂了。 +考虑下面这个例子: +``` C++ +struct A { int a; }; +struct AB { int a, b; }; +struct C { int c; }; + +template foo(T& v0, C& v1){ + v0.a = 1; + v1.a = 2; + v1.c = 3; +} +``` +简单分析上述代码很容易得到以下结论: + +1. 函数`foo`中的变量`v1`已经确定是`struct C`的实例,所以,`v1.a = 2;`会导致编译错误,`v1.c = 3;`是正确的代码; +2. 对于变量`v0`来说,这个问题就变得很微妙。如果`v0`是`struct A`或者`struct AB`的实例,那么`foo`中的语句`v0.a = 1;`就是正确的。如果是`struct C`,那么这段代码就是错误的。 + +因此在模板定义的地方进行语义分析,并不能**完全**得出代码是正确或者错误的结论,只有到了实例化阶段,确定了模版参数的类型后,才知道这段代码正确与否。令人高兴的是,在这一问题上,我们和C++标准委员会的见地一致,说明我们的C++水平已经和Herb Sutter不分伯仲了。既然我们和Herb Sutter水平差不多,那凭什么人家就吃香喝辣?下面我们来选几条标准看看服不服: + +> ###14.6 名称解析(Name resolution) +**1)** 模板定义中能够出现以下三类名称: + —— 模板名称、或模板实现中所定义的名称; + —— 和模板参数有关的名称; + —— 模板定义所在的定义域内能看到的名称。 + … +**9)** … 如果名字查找和模板参数有关,那么查找会延期到模板参数全都确定的时候。 … +**10)** 如果(模板定义内出现的)名字和模板参数无关,那么在模板定义处,就应该找得到这个名字的声明。… +> ###14.6.2 依赖性名称(Dependent names) +**1)** …(模板定义中的)表达式和类型可能会依赖于模板参数,并且模板参数会影响到名称查找的作用域 … 如果表达式中有操作数依赖于模板参数,那么整个表达式都依赖于模板参数,名称查找延期到**模板实例化时**进行。并且定义时和实例化时的上下文都会参与名称查找。(依赖性)表达式可以分为类型依赖(类型指模板参数的类型)或值依赖。 +> ####14.6.2.2 **类型依赖的表达式** +**2)** 如果成员函数所属的类型是和模板参数有关的,那么这个成员函数中的`this`就认为是类型依赖的。 +> ###14.6.3 非依赖性名称(Non-dependent names) +**1)** 非依赖性名称在**模板定义**时使用通常的名称查找规则进行名称查找。 + +[Working Draft: Standard of Programming Language C++, N3337][1] + +知道差距在哪了吗:人家会说黑话。什么时候咱们也会说黑话了,就是标准委员会成员了,反正懂得也不比他们少。不过黑话确实不太好懂 —— 怪我翻译不好的人,自己看原文,再说好懂了人家还靠什么吃饭 —— 我们来举一个例子: +```C++ +int a; +struct B { int v; } +template struct X { + B b; // B 是第三类名字,b 是第二类 + T t; // T 是第二类 + X* anthor; // X 这里代指 X,第一类 + typedef int Y; // int 是第三类 + Y y; // Y 是第一类 + C c; // C 什么都不是,编译错误。 + void foo() { + b.v += y; // b 是第一类,非依赖性名称 + b.v *= T::s_mem; // T::s_mem 是第二类 + // s_mem的作用域由T决定 + // 依赖性名称,类型依赖 + } +}; +``` + +所以,按照标准的意思,名称查找会在模板定义和实例化时各做一次,分别处理非依赖性名称和依赖性名称的查找。这就是“两阶段名称查找”这一名词的由来。只不过这个术语我也不知道是谁发明的,它并没有出现的标准上,但是频繁出现在StackOverflow和Blog上。 + +接下来,我们就来解决2.3.1节中留下的几个问题。 + +先看第四个问题。为什么MSVC中,模板函数的定义内不管填什么编译器都不报错?因为MSVC在分析模板定义时没有做任何事情。至于为啥连“大王叫我来巡山”都能过得去,这是C++语法/语义分析的特殊性导致的。 +C++是个非常复杂的语言,以至于它的编译器,不可能通过词法-语法-语义多趟分析清晰分割。因为它的语义将会直接干扰到语法: + +```C++ +void foo(){ + A b; +} +``` +在这段简短的代码中,就包含了两个歧义的可能,一是`A`是模板,于是`A`是一个实例化的类型,`b`是变量,另外一种就是关系表达式,`((A < T) > b)`。甚至词法分析也会受到语义的干扰,C++11中才明确被修正的`vector>`,就因为`>>`被误解为右移或流操作符,而导致某些编译器上的错误。因此,在语义没有确定之前,连语法都没有分析的价值。 +大约是基于如此考量,为了偷懒,MSVC将包括所有的语法/语义分析工作都挪到了第二个Phase,于是乎连带着语法分析都送进了第二个阶段。符合标准么?显然不符合。 +但是这里值得一提的是,MSVC的做法和标准相比,虽然投机取巧,但并非有弊无利。我们来先说一说坏处。考虑以下例子: +```C++ +// ----------- X.h ------------ + +template struct X { + // 实现代码 +}; + +// ---------- X.cpp ----------- + +// ... 一些代码 ... +X xi; +// ... 一些代码 ... +X xf; +// ... 一些代码 ... +``` +此时如果X中有一些与模板参数无关的错误,如果名称查找/语义分析在两个阶段完成,那么这些错误会很早、且唯一的被提示出来;但是如果一切都在实例化时处理,那么可能会导致不同的实例化过程提示同样的错误。而模板在运用过程中,往往会产生很多实例,此时便会大量报告同样的错误。 +当然,MSVC并不会真的这么做。根据推测,最终他们是合并了相同的错误。因为即便对于模板参数相关的编译错误,也只能看到最后一次实例化的错误信息: +``` +template struct X {}; + +template struct Y +{ + typedef X ReboundType; // 类型定义1 + void foo() + { + X instance0; + X::MemberType instance1; + WTF instance2 + } +}; + +void poo(){ + X::foo(); + X::foo(); +} +``` + +MSVC下和模板相关的错误只有一个: +``` +error C2039: 'MemberType': is not a member of 'X' + with + [ + T=float + ] +``` +然后是一些语法错误,比如`MemberType`不是一个合法的标识符之类的。这样甚至你会误以为`int`情况下模板的实力化是正确的。虽然在有了经验之后会发现这个问题挺荒唐的,但是仍然会让新手有困惑。 + +相比之下,更加遵守标准的Clang在错误提示上就要清晰许多: + +``` +error: unknown type name 'WTF' + WTF instance2 + ^ +error: expected ';' at end of declaration + WTF instance2 + ^ + ; +error: no type named 'MemberType' in 'X' + typename X::MemberType instance1; + ~~~~~~~~~~~~~~~^~~~~~~~~~ + note: in instantiation of member function 'Y::foo' requested here + Y::foo(); + ^ +error: no type named 'MemberType' in 'X' + typename X::MemberType instance1; + ~~~~~~~~~~~~~~~^~~~~~~~~~ + note: in instantiation of member function 'Y::foo' requested here + Y::foo(); + ^ +4 errors generated. +``` +可以看到,Clang的提示和标准更加契合。它很好地区分了模板在定义和实例化时分别产生的错误。 +另一个缺点也与之类似。因为没有足够的检查,如果你写的模板没有被实例化,那么很可能缺陷会一直存在于代码之中。特别是模板代码多在头文件。虽然不如接口那么重要,但也是属于被公开的部分,别人很可能会踩到坑上。缺陷一旦传播开修复起来就没那么容易了。 + +但是正如我前面所述,这个违背了标准的特性,并不是一无是处。首先,它可以完美的兼容标准。符合标准的、能够被正确编译的代码,一定能够被MSVC的方案所兼容。其次,它带来了一个非常有趣的特性,看下面这个例子: + +```C++ +struct A; +template struct X { + void foo(T v) { + A a; + a.v = v; + } +}; + +struct A { int v; }; + +void main() { + X x; + x.foo(5); +} +``` +这个例子在Clang中是错误的,因为: +``` +error: variable has incomplete type 'A' + A a; + ^ + note: forward declaration of 'A' + struct A; + ^ +1 error generated. +``` + +符合标准的写法需要将模板类的定义,和模板函数的定义分离开: + +```C++ +struct A; +template struct X { + void foo(T v) { + A a; + a.v = v; + } +}; + +struct A { int v; }; + +template void X::foo(T v) { + A a; + a.v = v; +} + +void main() { + X x; + x.foo(5); +} +``` + +但是其实我们知道,`foo`要到实例化之后,才需要真正的做语义分析。在MSVC上,因为函数实现就是到模板实例化时才处理的,所以这个例子是完全正常工作的。 +在实际应用中,我们经常既希望把模板类成员函数的声明和实现放到一起,因为模板函数看不到实现也很难调用;又希望一般类型可以声明定义分离,把类型定义隐藏到源文件中,以完成声明实现分离。 +这个时候,对于符合标准的编译器,我们只能将模板头文件拆分成``和``两个部分,并按照顺序引用两个文件。但是在MSVC中就可以直接将模板函数的实现,和一般类型的声明放在一起,反而更加简单清晰。 + +扩展阅读: [The Dreaded Two-Phase Name Lookup][2] ###2.4 函数模板的重载、参数匹配、特化与部分特化 ###2. 技巧单元:模板与继承 @@ -1314,3 +1558,7 @@ alexandrescu 关于 min max 的讨论:《再谈Min和Max》 ###7.5 更高更快更强:从Linq到FP ## 8 结语:讨论有益,争端无用 + + + [1]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf + [2]: http://blog.llvm.org/2009/12/dreaded-two-phase-name-lookup.html \ No newline at end of file From 0fff42ce4b73149ea092e780979e3cbeaf43d8c5 Mon Sep 17 00:00:00 2001 From: Ye WU Date: Tue, 24 Nov 2015 21:19:23 -0800 Subject: [PATCH 27/99] Update document format. --- ReadMe.md | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index ce3b07d..fbd1af5 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1335,19 +1335,21 @@ template foo(T& v0, C& v1){ 因此在模板定义的地方进行语义分析,并不能**完全**得出代码是正确或者错误的结论,只有到了实例化阶段,确定了模版参数的类型后,才知道这段代码正确与否。令人高兴的是,在这一问题上,我们和C++标准委员会的见地一致,说明我们的C++水平已经和Herb Sutter不分伯仲了。既然我们和Herb Sutter水平差不多,那凭什么人家就吃香喝辣?下面我们来选几条标准看看服不服: > ###14.6 名称解析(Name resolution) -**1)** 模板定义中能够出现以下三类名称: - —— 模板名称、或模板实现中所定义的名称; - —— 和模板参数有关的名称; - —— 模板定义所在的定义域内能看到的名称。 - … -**9)** … 如果名字查找和模板参数有关,那么查找会延期到模板参数全都确定的时候。 … -**10)** 如果(模板定义内出现的)名字和模板参数无关,那么在模板定义处,就应该找得到这个名字的声明。… + +> **1)** 模板定义中能够出现以下三类名称: + +> —— 模板名称、或模板实现中所定义的名称; +> —— 和模板参数有关的名称; +> —— 模板定义所在的定义域内能看到的名称。 +> … +> **9)** … 如果名字查找和模板参数有关,那么查找会延期到模板参数全都确定的时候。 … +> **10)** 如果(模板定义内出现的)名字和模板参数无关,那么在模板定义处,就应该找得到这个名字的声明。… > ###14.6.2 依赖性名称(Dependent names) -**1)** …(模板定义中的)表达式和类型可能会依赖于模板参数,并且模板参数会影响到名称查找的作用域 … 如果表达式中有操作数依赖于模板参数,那么整个表达式都依赖于模板参数,名称查找延期到**模板实例化时**进行。并且定义时和实例化时的上下文都会参与名称查找。(依赖性)表达式可以分为类型依赖(类型指模板参数的类型)或值依赖。 +> **1)** …(模板定义中的)表达式和类型可能会依赖于模板参数,并且模板参数会影响到名称查找的作用域 … 如果表达式中有操作数依赖于模板参数,那么整个表达式都依赖于模板参数,名称查找延期到**模板实例化时**进行。并且定义时和实例化时的上下文都会参与名称查找。(依赖性)表达式可以分为类型依赖(类型指模板参数的类型)或值依赖。 > ####14.6.2.2 **类型依赖的表达式** -**2)** 如果成员函数所属的类型是和模板参数有关的,那么这个成员函数中的`this`就认为是类型依赖的。 +> **2)** 如果成员函数所属的类型是和模板参数有关的,那么这个成员函数中的`this`就认为是类型依赖的。 > ###14.6.3 非依赖性名称(Non-dependent names) -**1)** 非依赖性名称在**模板定义**时使用通常的名称查找规则进行名称查找。 +> **1)** 非依赖性名称在**模板定义**时使用通常的名称查找规则进行名称查找。 [Working Draft: Standard of Programming Language C++, N3337][1] @@ -1384,8 +1386,12 @@ void foo(){ A b; } ``` -在这段简短的代码中,就包含了两个歧义的可能,一是`A`是模板,于是`A`是一个实例化的类型,`b`是变量,另外一种就是关系表达式,`((A < T) > b)`。甚至词法分析也会受到语义的干扰,C++11中才明确被修正的`vector>`,就因为`>>`被误解为右移或流操作符,而导致某些编译器上的错误。因此,在语义没有确定之前,连语法都没有分析的价值。 +在这段简短的代码中,就包含了两个歧义的可能,一是`A`是模板,于是`A`是一个实例化的类型,`b`是变量,另外一种就是关系表达式,`((A < T) > b)`。 + +甚至词法分析也会受到语义的干扰,C++11中才明确被修正的`vector>`,就因为`>>`被误解为右移或流操作符,而导致某些编译器上的错误。因此,在语义没有确定之前,连语法都没有分析的价值。 + 大约是基于如此考量,为了偷懒,MSVC将包括所有的语法/语义分析工作都挪到了第二个Phase,于是乎连带着语法分析都送进了第二个阶段。符合标准么?显然不符合。 + 但是这里值得一提的是,MSVC的做法和标准相比,虽然投机取巧,但并非有弊无利。我们来先说一说坏处。考虑以下例子: ```C++ // ----------- X.h ------------ @@ -1403,6 +1409,7 @@ X xf; // ... 一些代码 ... ``` 此时如果X中有一些与模板参数无关的错误,如果名称查找/语义分析在两个阶段完成,那么这些错误会很早、且唯一的被提示出来;但是如果一切都在实例化时处理,那么可能会导致不同的实例化过程提示同样的错误。而模板在运用过程中,往往会产生很多实例,此时便会大量报告同样的错误。 + 当然,MSVC并不会真的这么做。根据推测,最终他们是合并了相同的错误。因为即便对于模板参数相关的编译错误,也只能看到最后一次实例化的错误信息: ``` template struct X {}; @@ -1459,6 +1466,7 @@ error: no type named 'MemberType' in 'X' 4 errors generated. ``` 可以看到,Clang的提示和标准更加契合。它很好地区分了模板在定义和实例化时分别产生的错误。 + 另一个缺点也与之类似。因为没有足够的检查,如果你写的模板没有被实例化,那么很可能缺陷会一直存在于代码之中。特别是模板代码多在头文件。虽然不如接口那么重要,但也是属于被公开的部分,别人很可能会踩到坑上。缺陷一旦传播开修复起来就没那么容易了。 但是正如我前面所述,这个违背了标准的特性,并不是一无是处。首先,它可以完美的兼容标准。符合标准的、能够被正确编译的代码,一定能够被MSVC的方案所兼容。其次,它带来了一个非常有趣的特性,看下面这个例子: @@ -1515,8 +1523,10 @@ void main() { ``` 但是其实我们知道,`foo`要到实例化之后,才需要真正的做语义分析。在MSVC上,因为函数实现就是到模板实例化时才处理的,所以这个例子是完全正常工作的。 + 在实际应用中,我们经常既希望把模板类成员函数的声明和实现放到一起,因为模板函数看不到实现也很难调用;又希望一般类型可以声明定义分离,把类型定义隐藏到源文件中,以完成声明实现分离。 -这个时候,对于符合标准的编译器,我们只能将模板头文件拆分成``和``两个部分,并按照顺序引用两个文件。但是在MSVC中就可以直接将模板函数的实现,和一般类型的声明放在一起,反而更加简单清晰。 + +此时如果编译器是符合标准的,我们只能将模板头文件拆分成``和``两个部分,并按照顺序引用两个文件。但是在MSVC中就可以直接将模板函数的实现,和一般类型的声明放在一起,反而更加简单清晰。 扩展阅读: [The Dreaded Two-Phase Name Lookup][2] ###2.4 函数模板的重载、参数匹配、特化与部分特化 From 24fbf47ad3f22e332f1444b97855e840f07c06c9 Mon Sep 17 00:00:00 2001 From: Ye WU Date: Tue, 24 Nov 2015 21:23:37 -0800 Subject: [PATCH 28/99] Updated format of referenced text. --- ReadMe.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index fbd1af5..d37900e 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1334,21 +1334,30 @@ template foo(T& v0, C& v1){ 因此在模板定义的地方进行语义分析,并不能**完全**得出代码是正确或者错误的结论,只有到了实例化阶段,确定了模版参数的类型后,才知道这段代码正确与否。令人高兴的是,在这一问题上,我们和C++标准委员会的见地一致,说明我们的C++水平已经和Herb Sutter不分伯仲了。既然我们和Herb Sutter水平差不多,那凭什么人家就吃香喝辣?下面我们来选几条标准看看服不服: -> ###14.6 名称解析(Name resolution) +> **14.6 名称解析(Name resolution)** > **1)** 模板定义中能够出现以下三类名称: -> —— 模板名称、或模板实现中所定义的名称; -> —— 和模板参数有关的名称; -> —— 模板定义所在的定义域内能看到的名称。 +> * 模板名称、或模板实现中所定义的名称; +> * 和模板参数有关的名称; +> * 模板定义所在的定义域内能看到的名称。 + > … + > **9)** … 如果名字查找和模板参数有关,那么查找会延期到模板参数全都确定的时候。 … + > **10)** 如果(模板定义内出现的)名字和模板参数无关,那么在模板定义处,就应该找得到这个名字的声明。… -> ###14.6.2 依赖性名称(Dependent names) -> **1)** …(模板定义中的)表达式和类型可能会依赖于模板参数,并且模板参数会影响到名称查找的作用域 … 如果表达式中有操作数依赖于模板参数,那么整个表达式都依赖于模板参数,名称查找延期到**模板实例化时**进行。并且定义时和实例化时的上下文都会参与名称查找。(依赖性)表达式可以分为类型依赖(类型指模板参数的类型)或值依赖。 -> ####14.6.2.2 **类型依赖的表达式** + +> **14.6.2 依赖性名称(Dependent names)** + +> **1)** …(模板定义中的)表达式和类型可能会依赖于模板参数,并且模板参数会影响到名称查找的作用域 … 如果表达式中有操作数依赖于模板参数,那么整个表达式都依赖于模板参数,名称查找延期到**模板实例化时**进行。并且定义时和实例化时的上下文都会参与名称查找。(依赖性)表达式可以分为类型依赖(类型指模板参数的类型)或值依赖。 + +> **14.6.2.2 类型依赖的表达式** + > **2)** 如果成员函数所属的类型是和模板参数有关的,那么这个成员函数中的`this`就认为是类型依赖的。 -> ###14.6.3 非依赖性名称(Non-dependent names) + +> **14.6.3 非依赖性名称(Non-dependent names)** + > **1)** 非依赖性名称在**模板定义**时使用通常的名称查找规则进行名称查找。 [Working Draft: Standard of Programming Language C++, N3337][1] From 7819d831fdc4dac3a2b8b486ddec1d58e1956593 Mon Sep 17 00:00:00 2001 From: Ye WU Date: Wed, 2 Dec 2015 16:22:16 -0800 Subject: [PATCH 29/99] =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E7=AB=A0=E7=BB=93?= =?UTF-8?q?=E6=9D=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 122 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 100 insertions(+), 22 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index d37900e..5315ce3 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -949,8 +949,8 @@ class AddFloatOrMulInt // 以及模板参数列表 template class AddFloatOrMulInt; -// 最后,模板参数列表里面填什么?因为原型的T已经被int取代了。所以这里就不能放任何额外的参数了。 -// 所以这里要放空。 +// 最后,模板参数列表里面填什么?因为原型的T已经被int取代了。所以这里就不能也不需要放任何额外的参数了。 +// 所以这里放空。 template <> class AddFloatOrMulInt { // ... 针对Int的实现 ... @@ -1090,8 +1090,8 @@ void copy(void* dst, void const* src, size_t elemSize, size_t elemCount, void (* ``` 为什么要提供copyElem,是因为可能有些struct需要深拷贝,所以得用特殊的copy函数。这个在C++98/03里面就体现为拷贝构造和赋值函数。 -但是不管怎么搞,因为这个函数的参数只是`void*`而已,当你使用了错误的elemSize,或者传入了错误的copyElem,就必须要到运行的时候才有可能看出来。注意,这还只是有可能而已。 +但是不管怎么搞,因为这个函数的参数只是`void*`而已,当你使用了错误的elemSize,或者传入了错误的copyElem,就必须要到运行的时候才有可能看出来。注意,这还只是有可能而已。 那么C++有了模板后,能否既能匹配任意类型的指针,同时又保留了类型信息呢?答案是显然的。至于怎么写,那就得充分发挥你的直觉了: @@ -1115,6 +1115,7 @@ void copy(T* dst, T const* src, size_t elemCount); ``` 编译一下,咦,居然通过了。看来这里的语法与我们以前学到的知识并没有什么不同。这也是语言设计最重要的一点原则:一致性。它可以让你辛辛苦苦体验到的规律不至于白费。 + 最后就是实现: ``` C++ @@ -1387,7 +1388,7 @@ template struct X { 接下来,我们就来解决2.3.1节中留下的几个问题。 -先看第四个问题。为什么MSVC中,模板函数的定义内不管填什么编译器都不报错?因为MSVC在分析模板定义时没有做任何事情。至于为啥连“大王叫我来巡山”都能过得去,这是C++语法/语义分析的特殊性导致的。 +先看第四个问题。为什么MSVC中,模板函数的定义内不管填什么编译器都不报错?因为MSVC在分析模板中成员函数定义时没有做任何事情。至于为啥连“大王叫我来巡山”都能过得去,这是C++语法/语义分析的特殊性导致的。 C++是个非常复杂的语言,以至于它的编译器,不可能通过词法-语法-语义多趟分析清晰分割。因为它的语义将会直接干扰到语法: ```C++ @@ -1399,7 +1400,7 @@ void foo(){ 甚至词法分析也会受到语义的干扰,C++11中才明确被修正的`vector>`,就因为`>>`被误解为右移或流操作符,而导致某些编译器上的错误。因此,在语义没有确定之前,连语法都没有分析的价值。 -大约是基于如此考量,为了偷懒,MSVC将包括所有的语法/语义分析工作都挪到了第二个Phase,于是乎连带着语法分析都送进了第二个阶段。符合标准么?显然不符合。 +大约是基于如此考量,为了偷懒,MSVC将包括所有模板成员函数的语法/语义分析工作都挪到了第二个Phase,于是乎连带着语法分析都送进了第二个阶段。符合标准么?显然不符合。 但是这里值得一提的是,MSVC的做法和标准相比,虽然投机取巧,但并非有弊无利。我们来先说一说坏处。考虑以下例子: ```C++ @@ -1483,9 +1484,9 @@ error: no type named 'MemberType' in 'X' ```C++ struct A; template struct X { - void foo(T v) { - A a; - a.v = v; + int v; + void convertTo(A& a) { + a.v = v; // 这里需要A的实现 } }; @@ -1512,16 +1513,13 @@ error: variable has incomplete type 'A' ```C++ struct A; template struct X { - void foo(T v) { - A a; - a.v = v; - } + int v; + void convertTo(A& a); }; struct A { int v; }; -template void X::foo(T v) { - A a; +template void X::convertTo(A& a) { a.v = v; } @@ -1531,18 +1529,97 @@ void main() { } ``` -但是其实我们知道,`foo`要到实例化之后,才需要真正的做语义分析。在MSVC上,因为函数实现就是到模板实例化时才处理的,所以这个例子是完全正常工作的。 +但是其实我们知道,`foo`要到实例化之后,才需要真正的做语义分析。在MSVC上,因为函数实现就是到模板实例化时才处理的,所以这个例子是完全正常工作的。因此在上面这个例子中,MSVC的实现要比标准更加易于写和维护,是不是有点写Java/C#那种声明实现都在同一处的清爽感觉了呢! -在实际应用中,我们经常既希望把模板类成员函数的声明和实现放到一起,因为模板函数看不到实现也很难调用;又希望一般类型可以声明定义分离,把类型定义隐藏到源文件中,以完成声明实现分离。 +扩展阅读: [The Dreaded Two-Phase Name Lookup][2] -此时如果编译器是符合标准的,我们只能将模板头文件拆分成``和``两个部分,并按照顺序引用两个文件。但是在MSVC中就可以直接将模板函数的实现,和一般类型的声明放在一起,反而更加简单清晰。 +#2.3.3 “多余的” typename 关键字 -扩展阅读: [The Dreaded Two-Phase Name Lookup][2] -###2.4 函数模板的重载、参数匹配、特化与部分特化 -###2. 技巧单元:模板与继承 +到了这里,2.3.1 中提到的四个问题,还有三个没有解决: + +```C++ +template struct X {}; + +template struct Y +{ + typedef X ReboundType; // 这里为什么是正确的? + typedef typename X::MemberType MemberType2; // 这里的typename是做什么的? + typedef UnknownType MemberType3; // 这里为什么会出错? +}; +``` + +我们运用我们2.3.2节中学习到的标准,来对Y内部做一下分析: + +``` +template struct Y +{ + // X可以查找到原型; + // X是一个依赖性名称,模板定义阶段并不管X是不是正确的。 + typedef X ReboundType; + + // X可以查找到原型; + // X是一个依赖性名称,X::MemberType也是一个依赖性名称; + // 所以模板声明时也不会管X模板里面有没有MemberType这回事。 + typedef typename X::MemberType MemberType2; + + // UnknownType 不是一个依赖性名称 + // 而且这个名字在当前作用域中不存在,所以直接报错。 + typedef UnknownType MemberType3; +}; +``` + +下面,唯一的问题就是第二个:`typename`是做什么的? + +对于用户来说,这其实是一个语法噪音。也就是说,其实就算没有它,语法上也说得过去。事实上,某些情况下MSVC的确会在标准需要的时候,不用写`typename`。但是标准中还是规定了形如 `T::MemberType` 这样的`qualified id` 在默认情况下不是一个类型,而是解释为`T`的一个成员变量`MemberType`,只有当`typename`修饰之后才能作为类型出现。 + +事实上,标准对`typename`的使用规定极为复杂,也算是整个模板中的难点之一。如果想了解所有的标准,需要阅读标准14.6节下2-7条,以及14.6.2.1第一条中对于`current instantiation`的解释。 + +简单来说,如果编译器能在出现的时候知道它的类型,那么就不需要`typename`,如果必须要到实例化的时候才能知道它是不是合法,那么定义的时候就把这个名称作为变量而不是类型。 + +在这里,我举几个例子帮助大家理解`typename`的用法,这几个例子已经足以涵盖日常使用[(预览)][3]: + +```C++ +struct A; +template struct B; +template struct X { + typedef X _A; // 编译器当然知道 X 是一个类型。 + typedef X _B; // X 等价于 X 的缩写 + typedef T _C; // T 不是一个类型还玩毛 + + // !!!注意我要变形了!!! + class Y { + typedef X _D; // X 的内部,既然外部高枕无忧,内部更不用说了 + typedef X::Y _E; // 嗯,这里也没问题,编译器知道Y就是当前的类型, + // 这里在VS2015上会有错,需要添加 typename, + // Clang 上顺利通过。 + typedef typename X::Y _F; // 这个居然要加 typename! + // 因为,X和X不一样哦, + // 它可能会在实例化的时候被别的偏特化给抢过去实现了。 + }; + + typedef A _G; // 嗯,没问题,A在外面声明啦 + typedef B _H; // B也是一个类型 + typedef typename B::type _I; // 嗯,因为不知道B::type的信息, + // 所以需要typename + typedef B::type _J; // B 不依赖模板参数, + // 所以编译器直接就实例化(instantiate)了 + // 但是这个时候,B并没有被实现,所以就出错了 +}; +``` + +### 2.4 本章小结 + +这一张是写作中最艰难的一章,中间停滞了将近一年。因为要说清楚C++模板中一些语法噪音和设计决议并不是一件轻松的事情。不过通过这一章的学习,我们知道了下面这几件事情: + +1. **部分特化/偏特化** 和 **特化** 相当于是模板实例化过程中的`if-then-else`。这使得我们根据不同类型,选择不同实现的需求得以实现; + +2. 在 2.3.3 一节我们插入了C++模板中最难理解的内容之一:名称查找。名称查找是语义分析的一个环节,模板内书写的 **变量声明**、**typedef**、**类型名称** 甚至 **类模板中成员函数的实现** 都要符合名称查找的规矩才不会出错; + +3. C++编译器对语义的分析的原则是“大胆假设,小心求证”:在能求证的地方尽量求证 —— 比如两段式名称查找的第一阶段;无法检查的地方假设你是正确的 —— 比如`typedef typename A::MemberType _X;`在模板定义时因为`T`不明确不会轻易判定这个语句的死刑。 +从下一章开始,我们将进入元编程环节。我们将使用大量的示例,一方面帮助巩固大家学到的模板知识,一方面也会引导大家使用函数式思维去解决常见的问题。 -## 3 拿起特化的武器,去写程序吧! +## 3 拿起武器,去写程序吧! ###3.1 利用模板特化规则实现If-Then-Else与Switch-Case ###3.2 特化可以有多个选择:替换失败并不是一个错误,只是一种可能 ###3.3 技巧单元:获得类型的属性——类型萃取(Type Traits) @@ -1580,4 +1657,5 @@ alexandrescu 关于 min max 的讨论:《再谈Min和Max》 [1]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf - [2]: http://blog.llvm.org/2009/12/dreaded-two-phase-name-lookup.html \ No newline at end of file + [2]: http://blog.llvm.org/2009/12/dreaded-two-phase-name-lookup.html + [3]: https://goo.gl/zCRNYx \ No newline at end of file From ebfe1dfb910df2481550b68c1159eca8e966f057 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Wed, 2 Dec 2015 17:45:31 -0800 Subject: [PATCH 30/99] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E7=A4=BA=E4=BE=8B=E7=9A=84=E6=A0=BC=E5=BC=8F=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 5315ce3..34f1aa2 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1550,7 +1550,7 @@ template struct Y 我们运用我们2.3.2节中学习到的标准,来对Y内部做一下分析: -``` +```C++ template struct Y { // X可以查找到原型; @@ -1658,4 +1658,4 @@ alexandrescu 关于 min max 的讨论:《再谈Min和Max》 [1]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf [2]: http://blog.llvm.org/2009/12/dreaded-two-phase-name-lookup.html - [3]: https://goo.gl/zCRNYx \ No newline at end of file + [3]: https://goo.gl/zCRNYx From d3cc0592182cef56e54a56d4955983cace5d1643 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Wed, 2 Dec 2015 18:32:49 -0800 Subject: [PATCH 31/99] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BA=862.3.3=E6=A0=87?= =?UTF-8?q?=E9=A2=98=E7=9A=84=E6=A0=BC=E5=BC=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 34f1aa2..310b3a8 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1533,7 +1533,7 @@ void main() { 扩展阅读: [The Dreaded Two-Phase Name Lookup][2] -#2.3.3 “多余的” typename 关键字 +####2.3.3 “多余的” typename 关键字 到了这里,2.3.1 中提到的四个问题,还有三个没有解决: From a46f244c9637434040930d7eef2a1a7b30598d71 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Wed, 2 Dec 2015 19:12:36 -0800 Subject: [PATCH 32/99] =?UTF-8?q?=E4=BF=AE=E8=AE=A2=E4=BA=862.1=E4=B8=AD?= =?UTF-8?q?=E5=AE=B9=E6=98=93=E5=AF=BC=E8=87=B4=E6=AD=A7=E4=B9=89=E7=9A=84?= =?UTF-8?q?=E4=BE=8B=E5=AD=90=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close #5. --- ReadMe.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 310b3a8..6997359 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -618,9 +618,9 @@ typedef Stack StackFloat; ``` Int8,16: N/A -Int32 : VInt32Mul(int32 * 4, int32 * 4) -Int64 : VInt64Mul(int64 * 2, int64 * 2) -Float : VInt64Mul(float * 2, float * 2) +Int32 : VInt32Mul(int32x4, int32x4) +Int64 : VInt64Mul(int64x4, int64x4) +Float : VInt64Mul(floatx2, floatx2) ``` 所以对于Int8和Int16,我们需要提升到Int32,而Int32和Int64,各自使用自己的指令。所以我们需要实现下的逻辑: @@ -641,10 +641,10 @@ for(v4a, v4b : vectorsA, vectorsB) 嗯,聪明你果然想到了,重载也可以解决这个问题。 ``` C++ -GenericMul(int8 * 4, int8 * 4); -GenericMul(int16 * 4, int16 * 4); -GenericMul(int32 * 4, int32 * 4); -GenericMul(int64 * 4, int64 * 4); +GenericMul(int8x4, int8x4); +GenericMul(int16x4, int16x4); +GenericMul(int32x4, int32x4); +GenericMul(int64x4, int64x4); // 其它 Generic Mul ... for(v4a, v4b : vectorsA, vectorsB) From c9c3521eb9b8294b64752b2824226d29ff0354f4 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Thu, 3 Dec 2015 00:45:23 -0800 Subject: [PATCH 33/99] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E5=A4=84=E7=94=A8=E8=AF=AD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 6997359..c9c5b66 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1396,7 +1396,7 @@ void foo(){ A b; } ``` -在这段简短的代码中,就包含了两个歧义的可能,一是`A`是模板,于是`A`是一个实例化的类型,`b`是变量,另外一种就是关系表达式,`((A < T) > b)`。 +在这段简短的代码中,就包含了两个歧义的可能,一是`A`是模板,于是`A`是一个实例化的类型,`b`是变量,另外一种是比较表达式(Comparison Expression)的组合,`((A < T) > b)`。 甚至词法分析也会受到语义的干扰,C++11中才明确被修正的`vector>`,就因为`>>`被误解为右移或流操作符,而导致某些编译器上的错误。因此,在语义没有确定之前,连语法都没有分析的价值。 From baf962c80fddcb33258d0c7e7c05200e5fe79f23 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Sun, 6 Dec 2015 22:51:07 -0800 Subject: [PATCH 34/99] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E5=90=8E?= =?UTF-8?q?=E5=87=A0=E7=AB=A0=E7=9A=84=E5=86=99=E4=BD=9C=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index c9c5b66..048f05d 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1619,32 +1619,32 @@ template struct X { 从下一章开始,我们将进入元编程环节。我们将使用大量的示例,一方面帮助巩固大家学到的模板知识,一方面也会引导大家使用函数式思维去解决常见的问题。 -## 3 拿起武器,去写程序吧! -###3.1 利用模板特化规则实现If-Then-Else与Switch-Case -###3.2 特化可以有多个选择:替换失败并不是一个错误,只是一种可能 -###3.3 技巧单元:获得类型的属性——类型萃取(Type Traits) +## 3 深入理解特化 + +###3.1 利用偏特化实现If-Then-Else +###3.2 正确的理解偏特化 +###3.3 后悔药:SFINAE +###3.4 技巧单元:获得类型的属性——类型萃取(Type Traits) ## 4 用模板写程序吧!骚年! ###4.1 模板上的递归 ###4.2 将循环变成递归,将分支变成递归,将一切变成递归 ###4.3 实战单元:元编程的Fibonacci数列 -###4.4 技巧单元:typename与template的另一种用法 -###4.5 实战单元:撰写你自己的元编程“函数”库 -###4.6 实战单元:实现元编程上的数据结构——以Vector为例 - -## 5 关于模板,你还需要知道的其它常识 -###5.1 类中类:灵活的模板定义 -###5.2 Template-Template Class -###5.3 技巧单元:高阶函数——从函数到函数的组合 -###5.4 实战单元:STL中的Allocator Rebinder -###5.5 像看堆栈一样的看出错信息 -###5.6 模板的症结:易于实现,难于完美 -alexandrescu 关于 min max 的讨论:《再谈Min和Max》 +## 5 元编程下的算法 +###5.1 列表与数组 +###5.2 字典结构 +###5.3 “快速”排序 -## 6 C++11的新特性 -###6.1 变参模板 -###6.2 Lambda与模板程序 +## 6 关于模板,你还需要知道的其它常识 +###6.1 类中类:灵活的模板定义 +###6.2 Template-Template Class +###6.3 技巧单元:高阶函数——从函数到函数的组合 +###6.4 实战单元:STL中的Allocator Rebinder +###6.5 像看堆栈一样的看出错信息 +###6.6 模板的症结:易于实现,难于完美 + +alexandrescu 关于 min max 的讨论:《再谈Min和Max》 ## 7 模板的威力:从foreach, transform到Linq ###7.1 Foreach与Transform @@ -1655,7 +1655,6 @@ alexandrescu 关于 min max 的讨论:《再谈Min和Max》 ## 8 结语:讨论有益,争端无用 - [1]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf [2]: http://blog.llvm.org/2009/12/dreaded-two-phase-name-lookup.html [3]: https://goo.gl/zCRNYx From 56dccfc13304188c97afc3634ff8065d3145c023 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Sun, 6 Dec 2015 22:57:33 -0800 Subject: [PATCH 35/99] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E7=AC=AC?= =?UTF-8?q?=E4=B8=89=E7=AB=A0=E7=9A=84=E5=86=99=E4=BD=9C=E8=AE=A1=E5=88=92?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 048f05d..a8a2283 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1619,12 +1619,11 @@ template struct X { 从下一章开始,我们将进入元编程环节。我们将使用大量的示例,一方面帮助巩固大家学到的模板知识,一方面也会引导大家使用函数式思维去解决常见的问题。 -## 3 深入理解特化 +## 3 深入理解特化与偏特化 -###3.1 利用偏特化实现If-Then-Else -###3.2 正确的理解偏特化 -###3.3 后悔药:SFINAE -###3.4 技巧单元:获得类型的属性——类型萃取(Type Traits) +###3.1 正确的理解偏特化 +###3.2 后悔药:SFINAE +###3.3 实战单元:获得类型的属性——类型萃取(Type Traits) ## 4 用模板写程序吧!骚年! ###4.1 模板上的递归 From 6e39aa6bf5bf779567f0074c0483cbe8d9104e50 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Wed, 9 Dec 2015 13:19:37 -0800 Subject: [PATCH 36/99] Update ReadMe.md --- ReadMe.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index a8a2283..bed0af5 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1369,7 +1369,7 @@ template foo(T& v0, C& v1){ int a; struct B { int v; } template struct X { - B b; // B 是第三类名字,b 是第二类 + B b; // B 是第三类名字,b 是第一类 T t; // T 是第二类 X* anthor; // X 这里代指 X,第一类 typedef int Y; // int 是第三类 @@ -1640,7 +1640,7 @@ template struct X { ###6.2 Template-Template Class ###6.3 技巧单元:高阶函数——从函数到函数的组合 ###6.4 实战单元:STL中的Allocator Rebinder -###6.5 像看堆栈一样的看出错信息 +###6.5 更好的编译器,更友善的出错信息 ###6.6 模板的症结:易于实现,难于完美 alexandrescu 关于 min max 的讨论:《再谈Min和Max》 @@ -1650,6 +1650,7 @@ alexandrescu 关于 min max 的讨论:《再谈Min和Max》 ###7.2 Reactor风格的编程 ###7.3 Reactor与Linq ###7.4 Linq的C++实践 +###7.5 Boost.Hana ###7.5 更高更快更强:从Linq到FP ## 8 结语:讨论有益,争端无用 From 914d3e7915dc09f93c5bd3d04ce4f08b735685e2 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Sun, 13 Dec 2015 02:07:06 -0800 Subject: [PATCH 37/99] =?UTF-8?q?=E6=92=B0=E5=86=99=E4=BA=863.1=E8=8A=82?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=86=85=E5=AE=B9=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index bed0af5..aa99ead 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1622,6 +1622,117 @@ template struct X { ## 3 深入理解特化与偏特化 ###3.1 正确的理解偏特化 +在前面的章节中,我们介绍了偏特化的形式、也介绍了简单的用例。因为偏特化和函数重载存在着形式上的相似性,因此初学者便会借用重载的概念,来理解偏特化的行为。只是,重载和偏特化尽管相似但仍有差异。 + +我们来先看一个函数重载的例子: + +```C++ +void doWork(int); +void doWork(float); +void doWork(int, int); + +void f() { + doWork(0); + doWork(0.5f); + doWork(0, 0); +} +``` + +在这个例子中,我们展现了函数重载可以在两种条件下工作:参数数量相同、类型不同;参数数量不同。 + +仿照重载的形式,我们通过特化机制,试图实现一个模板的“重载”: + +```C++ +template struct DoWork; // (0) 这是原型 + +template <> struct DoWork {}; // (1) 这是 int 类型的"重载" +template <> struct DoWork {}; // (2) 这是 float 类型的"重载" +template <> struct DoWork {}; // (3) 这是 int, int 类型的“重载” + +void f(){ + DoWork i; + DoWork f; + DoWork ii; +} +``` + +这个例子在字面上“看起来”并没有什么问题,可惜编译器在编译的时候仍然提示出错了(http://goo.gl/zI42Zv): + +``` +5 : error: too many template arguments for class template 'DoWork' +template <> struct DoWork {}; // 这是 int, int 类型的“重载” +^ ~~~~ +1 : note: template is declared here +template struct DoWork {}; // 这是原型 +~~~~~~~~~~~~~~~~~~~~~ ^ +``` + +从编译出错的失望中冷静一下,在仔细看看函数特化/偏特化和一般模板的不同之处: + +```C++ +template class X {}; +template class X {}; +// ^^^^ 注意这里 +``` + +对,就是这个``,跟在X后面的小尾巴,决定了第二条语句是第一条语句的跟班。所以,第二条语句即“偏特化”,必须要符合X的基本形式,那就是只有一个参数。这也是为什么`DoWork`尝试以`template <> struct DoWork`的形式偏特化的时候,编译器会提示参数数量过多。 + +另外一方面,在类模板的实例化阶段,它并不会直接去寻找 `template <> struct DoWork`这个小跟班,而是会先找到基本形式,`template struct DoWork;`,然后再去寻找相应的特化。 + +我们以`DoWork i;`为例,尝试复原一下编译器完成整个模板匹配过程的场景,帮助大家理解。看以下示例代码: + +```C++ +template struct DoWork; // (0) 这是原型 + +template <> struct DoWork {}; // (1) 这是 int 类型的"重载" +template <> struct DoWork {}; // (2) 这是 float 类型的"重载" + +DoWork i; // (3) +``` + +1. 编译器分析(0), (1), (2)三句,得知(0)是模板的原型,(1),(2)两句是模板(0)匹配的特例。我们假设有两个字典,第一个字典存储了模板原型,我们称之为`TemplateDict`。第二个字典`TemplateSpecDict`,存储了模板原型所对应的特化/偏特化形式。所以编译器在这三句时,可以视作`TemplateDict.add(DoWork)`,以及 `TemplateSpecDict.get(DoWork).add(int);` 和 `TemplateSpecDict.get(DoWork).add(float);` + +2. (4) 试图以`int`实例化类模板`DoWork`。它会在TemplateDict中,找到`DoWork`,它有一个形式参数`T`接受类型,正好和我们实例化的要求相符合。并且此时`T`被推导为`int`。 + +3. 编译器这个时候就想了,那它会不会有针对int的特化呢?于是就去`TemplateSpecDict`中查找,发现果然有`DoWork`的存在,于是就使用了这个特例。 + +那么根据上面的步骤所展现的基本原理,我们就能知道了,特化形式`struct X`的这个小尾巴,也要和`X`的原型相匹配: + +```C++ +template struct X ; // 0 原型有两个类型参数 + +// 下面的这些偏特化的“小尾巴”也需要两个类型参数对应 +template struct X {}; // 1 +template struct X {}; // 2 +template struct X {}; // 3 +template struct X {}; // 4 +template struct X {}; // 5 +template struct X {}; // 6 +template struct X {}; // 7 + +template struct X, shared_ptr>; // 8 + +// 以下特化,分别对应哪个偏特化的实例? + +X v0; +X v1; +X v2; +X v3; +X v4; +X v5; +X v6; +X v7; +X v8; +``` + +在上面这段例子中,有几个值得注意之处。首先,偏特化时的模板参数,和原型的模板参数没有任何关系,它只为偏特化这一句服务。这也是为什么在特化的时候所有类型都确定了,我们就可以抛弃全部的模板参数,写出`template <> struct X`这样的形式。 + +其次,作为一个模式匹配,偏特化中展现出来的模式,就是它能被匹配的精髓。比如,`struct X`中,要求模板的两个参数必须是相同的类型。而`struct X`,则代表第二个模板类型参数必须是第一个模板类型参数的指针,比如`X`就能匹配上。当然,除了简单的指针、`const`和`volatile`修饰符,其他的类模板也可以作为偏特化时的“模式”出现,例如示例8,它要求传入同一个类型的`unique_ptr`和`shared_ptr`。 + +对于某些实例化,偏特化的选择并不是唯一的。比如v4的参数是``,能够匹配的就有三条规则,1,6和7。很显然,6还是比7好一些,因为能多匹配一个指针。但是1和6,就很难说清楚谁更好了。一个说明了两者类型相同;另外一个则说明了两者都是指针。所以在这里,编译器也没办法决定使用那个,只好爆出了编译器错误。 + +嘿嘿,自己上编译器看看吧(http://goo.gl/9UVzje)。 + ###3.2 后悔药:SFINAE ###3.3 实战单元:获得类型的属性——类型萃取(Type Traits) From f19fd83ef22085e3b023778b534c9eee0bb246a5 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Sun, 13 Dec 2015 02:12:06 -0800 Subject: [PATCH 38/99] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E8=A1=8C=E6=96=87=E9=94=99=E8=AF=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index aa99ead..827e9d9 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1675,7 +1675,7 @@ template class X {}; // ^^^^ 注意这里 ``` -对,就是这个``,跟在X后面的小尾巴,决定了第二条语句是第一条语句的跟班。所以,第二条语句即“偏特化”,必须要符合X的基本形式,那就是只有一个参数。这也是为什么`DoWork`尝试以`template <> struct DoWork`的形式偏特化的时候,编译器会提示参数数量过多。 +对,就是这个``,跟在X后面的小尾巴,决定了第二条语句是第一条语句的跟班。所以,第二条语句,即“偏特化”,必须要符合原型X的基本形式,那就是只有一个参数。这也是为什么`DoWork`尝试以`template <> struct DoWork`的形式偏特化的时候,编译器会提示参数数量过多。 另外一方面,在类模板的实例化阶段,它并不会直接去寻找 `template <> struct DoWork`这个小跟班,而是会先找到基本形式,`template struct DoWork;`,然后再去寻找相应的特化。 @@ -1692,16 +1692,17 @@ DoWork i; // (3) 1. 编译器分析(0), (1), (2)三句,得知(0)是模板的原型,(1),(2)两句是模板(0)匹配的特例。我们假设有两个字典,第一个字典存储了模板原型,我们称之为`TemplateDict`。第二个字典`TemplateSpecDict`,存储了模板原型所对应的特化/偏特化形式。所以编译器在这三句时,可以视作`TemplateDict.add(DoWork)`,以及 `TemplateSpecDict.get(DoWork).add(int);` 和 `TemplateSpecDict.get(DoWork).add(float);` -2. (4) 试图以`int`实例化类模板`DoWork`。它会在TemplateDict中,找到`DoWork`,它有一个形式参数`T`接受类型,正好和我们实例化的要求相符合。并且此时`T`被推导为`int`。 +2. (3) 试图以`int`实例化类模板`DoWork`。它会在`TemplateDict`中,找到`DoWork`,它有一个形式参数`T`接受类型,正好和我们实例化的要求相符合。并且此时`T`被推导为`int`。 -3. 编译器这个时候就想了,那它会不会有针对int的特化呢?于是就去`TemplateSpecDict`中查找,发现果然有`DoWork`的存在,于是就使用了这个特例。 +3. 编译器这个时候就想了,那它会不会有针对`int`的特化呢?于是就去`TemplateSpecDict`中查找,发现果然有`DoWork`的存在,就使用了这个特例。 -那么根据上面的步骤所展现的基本原理,我们就能知道了,特化形式`struct X`的这个小尾巴,也要和`X`的原型相匹配: +那么根据上面的步骤所展现的基本原理,我们随便来几个练习: ```C++ -template struct X ; // 0 原型有两个类型参数 - -// 下面的这些偏特化的“小尾巴”也需要两个类型参数对应 +template struct X ; // 0 + // 原型有两个类型参数 + // 所以下面的这些偏特化的“小尾巴” + // 也需要两个类型参数对应 template struct X {}; // 1 template struct X {}; // 2 template struct X {}; // 3 From 6c02de29335a7662cca6a2075ffeff590286d2fa Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Sun, 13 Dec 2015 14:40:39 -0800 Subject: [PATCH 39/99] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E6=8E=A8?= =?UTF-8?q?=E8=8D=90=E7=BC=96=E8=AF=91=E5=99=A8=E4=B8=80=E8=8A=82=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 827e9d9..ce4d27f 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -39,7 +39,17 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言, 全文所有为我所撰写的部分,作者均保留所有版权。如果有需要转帖或引用,还请注明出处并告知于我。 -###0.4 意见、建议、喷、补遗、写作计划 +###0.4 推荐编译环境 + +C++编译器众多,且对模板的支持可能存在细微差别。如果没有特别强调,本书行文过程中,使用了下列编译器来测试文中提供的代码和示例: + +* Clang 3.7 (x86) +* Visual Studio 2015 +* GCC 4.9.2 (x86) + +此外,部分复杂实例我们还在文中提供了在线的编译器预览以方便大家阅读和测试。在线编译器参见: http://gcc.godbolt.org/ 。 + +###0.5 意见、建议、喷、补遗、写作计划 * 需增加: * 模板的使用动机。 @@ -1732,7 +1742,9 @@ X v8; 对于某些实例化,偏特化的选择并不是唯一的。比如v4的参数是``,能够匹配的就有三条规则,1,6和7。很显然,6还是比7好一些,因为能多匹配一个指针。但是1和6,就很难说清楚谁更好了。一个说明了两者类型相同;另外一个则说明了两者都是指针。所以在这里,编译器也没办法决定使用那个,只好爆出了编译器错误。 -嘿嘿,自己上编译器看看吧(http://goo.gl/9UVzje)。 +其他的示例可以先自己推测一下, 再去编译器上尝试一番 (http://goo.gl/9UVzje)。 + +再回到第一个例子`DoWork`。 ###3.2 后悔药:SFINAE ###3.3 实战单元:获得类型的属性——类型萃取(Type Traits) From 111110c347df0c2e8bf72c7091ee3cd8e0a8dc0a Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Sun, 13 Dec 2015 20:56:28 -0800 Subject: [PATCH 40/99] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BA=863.1=E8=8A=82?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index ce4d27f..33056df 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1744,7 +1744,49 @@ X v8; 其他的示例可以先自己推测一下, 再去编译器上尝试一番 (http://goo.gl/9UVzje)。 -再回到第一个例子`DoWork`。 +不过这个时候也许你还不死心。有没有一种办法能够让最初的例子`DoWork`像重载一样的支持多个参数呢?答案当然是肯定的。 + +首先,首先我们要让模板实例化时的模板参数统一到相同形式上。逆向思维一下,虽然两个类型参数我们很难缩成一个参数,但是我们可以通过添加额外的参数,把一个扩展成两个呀。比如这样: + +```C++ +DoWork i; +DoWork f; +DoWork ii; +``` + +这时,我们就能写出统一的模板原型: + +```C++ +template struct DoWork; +``` + +继而偏特化/特化问题也解决了: + +```C++ +template <> struct DoWork {}; // (1) 这是 int 类型的"重载" +template <> struct DoWork {}; // (2) 这是 float 类型的"重载" +template <> struct DoWork {}; // (3) 这是 int, int 类型的“重载” +``` + +显而易见这个解决方案并不那么完美。首先,不管是偏特化还是用户实例化模板的时候,都需要多撰写好几个`void`,而且最长的那个参数越长,需要写的就越多;其次,如果我们的`DoWork`在程序维护的过程中新加入了一个参数列表更长的实例,那么最悲惨的事情就会发生 —— 原型、每一个偏特化、每一个实例化都要追加上`void`以凑齐新出现的实例所需要的参数数量。 + +所幸模板参数也有一个和函数参数相同的特性:默认实参(Default Arguments)。一个例子你们就看明白了: + +```C++ +template struct DoWork; + +template struct DoWork {}; +template <> struct DoWork {}; +template <> struct DoWork {}; +template <> struct DoWork {}; + +DoWork i; +DoWork f; +DoWork d; +DoWork ii; +``` + +所有参数不足,即原型中参数`T1`没有指定的地方,都由T1自己的默认参数`void`补齐了。 ###3.2 后悔药:SFINAE ###3.3 实战单元:获得类型的属性——类型萃取(Type Traits) From 5fdf0e7448b8a18c907222a478bcd61d13e9c771 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Sun, 13 Dec 2015 20:58:59 -0800 Subject: [PATCH 41/99] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E5=A4=84=E8=A1=8C=E6=96=87=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 33056df..41f36d2 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1770,7 +1770,7 @@ template <> struct DoWork {}; // (3) 这是 int, int 类型的“ 显而易见这个解决方案并不那么完美。首先,不管是偏特化还是用户实例化模板的时候,都需要多撰写好几个`void`,而且最长的那个参数越长,需要写的就越多;其次,如果我们的`DoWork`在程序维护的过程中新加入了一个参数列表更长的实例,那么最悲惨的事情就会发生 —— 原型、每一个偏特化、每一个实例化都要追加上`void`以凑齐新出现的实例所需要的参数数量。 -所幸模板参数也有一个和函数参数相同的特性:默认实参(Default Arguments)。一个例子你们就看明白了: +所幸模板参数也有一个和函数参数相同的特性:默认实参(Default Arguments)。只需要一个例子,你们就能看明白了(http://goo.gl/TtmcY9): ```C++ template struct DoWork; From 6c918c77cd907ac753bf6ad4705f98e247f11865 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Tue, 15 Dec 2015 00:11:22 -0800 Subject: [PATCH 42/99] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BA=86=203.1=20?= =?UTF-8?q?=E8=8A=82=E9=83=A8=E5=88=86=E5=86=85=E5=AE=B9=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index 41f36d2..14993fd 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1788,6 +1788,33 @@ DoWork ii; 所有参数不足,即原型中参数`T1`没有指定的地方,都由T1自己的默认参数`void`补齐了。 +但是这个方案仍然有些美中不足之处。 + +比如,尽管我们默认了所有无效的类型都以`void`结尾,所以正确的类型列表应该是类似于``这样的形态。但你阻止不了你的用户写出类似于``这样奇怪的类型参数列表。 + +其次,假设这段代码中有一个函数,它的参数使用了和类模板相同的参数列表类型,如下面这段代码: + +```C++ +template struct X { + static void call(T0 const& p0, T1 const& p1); // 0 +}; + +template struct X { + static void call(T0 const& p0); // 1 +}; + +void foo(){ + X::call(5); // 调用函数 1 + X::call(5, 0.5f); // 调用函数 0 +} +``` + +那么,每加一个参数就要多写一个偏特化的形式,甚至还要重复编写一些可以共享的实现。 + +为了解决这几个问题,在C++11中,引入了变参模板(Variadic Template)。 + + + ###3.2 后悔药:SFINAE ###3.3 实战单元:获得类型的属性——类型萃取(Type Traits) From 743778d9d63ac74084fdab8cb401ce4a5c73d66a Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Tue, 15 Dec 2015 19:11:26 -0800 Subject: [PATCH 43/99] =?UTF-8?q?=E9=87=8D=E7=BB=843.1=E4=B8=BA3.1.1=20-?= =?UTF-8?q?=203.1.3=E8=8A=82=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 14993fd..1588b79 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -763,7 +763,7 @@ int main() ``` -这点限制也粉碎了妄图用模板来包办工厂(Factory)甚至是反射的梦想。尽管在《Modern C++ Design》中(别问我为什么老举这本书,因为《C++ Templates》和《Generic Programming》我只是囫囵吞枣读过,基本不记得了)大量运用模板来简化工厂方法;同时C++11和14中的一些机制如Vardric Template更是让这一问题的解决更加彻底。但无论如何,光靠模板你就是写不出依靠类名或者ID变量产生类型实例的代码。 +这点限制也粉碎了妄图用模板来包办工厂(Factory)甚至是反射的梦想。尽管在《Modern C++ Design》中(别问我为什么老举这本书,因为《C++ Templates》和《Generic Programming》我只是囫囵吞枣读过,基本不记得了)大量运用模板来简化工厂方法;同时C++11和14中的一些机制如Variadic Template更是让这一问题的解决更加彻底。但无论如何,直到C++11/14,光靠模板你就是写不出依靠类名或者ID变量产生类型实例的代码。 所以说,从能力上来看,模板能做的事情都是编译期完成的。编译期完成的意思就是,当你编译一个程序的时候,所有的量就都已经确定了。比如下面的这个例子: @@ -1632,6 +1632,9 @@ template struct X { ## 3 深入理解特化与偏特化 ###3.1 正确的理解偏特化 + +####3.1.1 偏特化与函数重载的比较 + 在前面的章节中,我们介绍了偏特化的形式、也介绍了简单的用例。因为偏特化和函数重载存在着形式上的相似性,因此初学者便会借用重载的概念,来理解偏特化的行为。只是,重载和偏特化尽管相似但仍有差异。 我们来先看一个函数重载的例子: @@ -1711,7 +1714,7 @@ DoWork i; // (3) ```C++ template struct X ; // 0 // 原型有两个类型参数 - // 所以下面的这些偏特化的“小尾巴” + // 所以下面的这些偏特化的“小尾巴”(实参列表) // 也需要两个类型参数对应 template struct X {}; // 1 template struct X {}; // 2 @@ -1736,15 +1739,21 @@ X v7; X v8; ``` -在上面这段例子中,有几个值得注意之处。首先,偏特化时的模板参数,和原型的模板参数没有任何关系,它只为偏特化这一句服务。这也是为什么在特化的时候所有类型都确定了,我们就可以抛弃全部的模板参数,写出`template <> struct X`这样的形式。 +在上面这段例子中,有几个值得注意之处。首先,偏特化时的模板参数,和原型的模板参数没有任何关系,和原型不同,它的顺序完全不影响模式匹配的顺序,它只是偏特化模式,如``中`U`的声明,真正的模式,是由``体现出来的。 + +这也是为什么在特化的时候,当所有类型都已经确定,我们就可以抛弃全部的模板参数,写出`template <> struct X`这样的形式:因为所有列表中所有参数都确定了,就不需要额外的形式参数了。 -其次,作为一个模式匹配,偏特化中展现出来的模式,就是它能被匹配的精髓。比如,`struct X`中,要求模板的两个参数必须是相同的类型。而`struct X`,则代表第二个模板类型参数必须是第一个模板类型参数的指针,比如`X`就能匹配上。当然,除了简单的指针、`const`和`volatile`修饰符,其他的类模板也可以作为偏特化时的“模式”出现,例如示例8,它要求传入同一个类型的`unique_ptr`和`shared_ptr`。 +其次,作为一个模式匹配,偏特化的实参列表中展现出来的“样子”,就是它能被匹配的原因。比如,`struct X`中,要求模板的两个参数必须是相同的类型。而`struct X`,则代表第二个模板类型参数必须是第一个模板类型参数的指针,比如`X`就能匹配上。当然,除了简单的指针、`const`和`volatile`修饰符,其他的类模板也可以作为偏特化时的“模式”出现,例如示例8,它要求传入同一个类型的`unique_ptr`和`shared_ptr`。 对于某些实例化,偏特化的选择并不是唯一的。比如v4的参数是``,能够匹配的就有三条规则,1,6和7。很显然,6还是比7好一些,因为能多匹配一个指针。但是1和6,就很难说清楚谁更好了。一个说明了两者类型相同;另外一个则说明了两者都是指针。所以在这里,编译器也没办法决定使用那个,只好爆出了编译器错误。 其他的示例可以先自己推测一下, 再去编译器上尝试一番 (http://goo.gl/9UVzje)。 -不过这个时候也许你还不死心。有没有一种办法能够让最初的例子`DoWork`像重载一样的支持多个参数呢?答案当然是肯定的。 +#### 3.1.2 不定长的模板参数 + +不过这个时候也许你还不死心。有没有一种办法能够让例子`DoWork`像重载一样,支持对长度不一的参数列表分别偏特化/特化呢? + +答案当然是肯定的。 首先,首先我们要让模板实例化时的模板参数统一到相同形式上。逆向思维一下,虽然两个类型参数我们很难缩成一个参数,但是我们可以通过添加额外的参数,把一个扩展成两个呀。比如这样: @@ -1790,7 +1799,7 @@ DoWork ii; 但是这个方案仍然有些美中不足之处。 -比如,尽管我们默认了所有无效的类型都以`void`结尾,所以正确的类型列表应该是类似于``这样的形态。但你阻止不了你的用户写出类似于``这样奇怪的类型参数列表。 +比如,尽管我们默认了所有无效的类型都以`void`结尾,所以正确的类型列表应该是类似于``这样的形态。但你阻止不了你的用户写出类似于``这样不符合约定的类型参数列表。 其次,假设这段代码中有一个函数,它的参数使用了和类模板相同的参数列表类型,如下面这段代码: @@ -1811,9 +1820,51 @@ void foo(){ 那么,每加一个参数就要多写一个偏特化的形式,甚至还要重复编写一些可以共享的实现。 -为了解决这几个问题,在C++11中,引入了变参模板(Variadic Template)。 +不过不管怎么说,以长参数加默认参数的方式支持变长参数是可行的做法,这也是C++98/03时代的唯一选择。 + +例如,[Boost.Tuple](https://github.com/boostorg/tuple/blob/develop/include/boost/tuple/detail/tuple_basic.hpp)就使用了这个方法,支持了变长的Tuple: + +```C++ +// Tuple 的声明,来自 boost +template < + class T0 = null_type, class T1 = null_type, class T2 = null_type, + class T3 = null_type, class T4 = null_type, class T5 = null_type, + class T6 = null_type, class T7 = null_type, class T8 = null_type, + class T9 = null_type> +class tuple; + +// Tuple的一些用例 +tuple a; +tuple b; +tuple c; +tuple > d; +tuple, bool, void*> e; +``` + +此外,Boost.MPL也使用了这个手法将`boost::mpl::vector`映射到`boost::mpl::vector _n_`上。但是我们也看到了,这个方案的缺陷很明显:代码臃肿和潜在的正确性问题。此外,过度使用模板偏特化、大量冗余的类型参数也给编译器带来了沉重的负担。 + +为了缓解这些问题,在C++11中,引入了变参模板(Variadic Template)。我们来看看支持了变参模板的C++11是如何实现tuple的: + +```C++ +template class tuple; +``` + +是不是一下子简洁了很多!这里的`typename... Ts`相当于一个声明,是说`Ts`不是一个类型,而是一个不定常的类型列表。同C语言的不定长参数一样,它通常只能放在参数列表的最后。看下面的例子: + +```C++ +template class X {}; // (1) error! +template class Y {}; // (2) +template class Y {}; // (3) +template class Y {}; // (4) error! +``` + +为什么第(1)条语句会出错呢?(1)是模板原型,模板实例化时,要以它为基础和实例化时的类型实参相匹配。因为C++的模板是自左向右匹配的,所以不定长参数只能结尾。其他形式,无论写作`Ts, U`,或者是`Ts, V, Us,`,或者是`V, Ts, Us`都是不可取的。(4) 也存在同样的问题。 + +但是,为什么(3)中, 模板参数和(1)相同,都是`typename... Ts, typename U`,但是编译器却并没有报错呢? +答案在这一节的早些时候。(3)和(1)不同,它并不是模板的原型,它只是`Y`的一个偏特化。回顾我们在之前所提到的,偏特化时,模板参数列表并不代表匹配顺序,它们只是为偏特化的模式提供的声明,也就是说,它们的匹配顺序,只是按照``来,而之前的参数只是告诉你`Ts`是一个类型列表,而`U`是一个类型,排名不分先后。 +####3.1.3 模板的默认实参 ###3.2 后悔药:SFINAE ###3.3 实战单元:获得类型的属性——类型萃取(Type Traits) From 3b82848e8f723b9b0f498da6876ccec4a2a8064b Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Tue, 15 Dec 2015 19:19:17 -0800 Subject: [PATCH 44/99] =?UTF-8?q?=E4=BF=AE=E6=94=B9=203.1.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 1588b79..d050baa 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1864,6 +1864,8 @@ template class Y {}; // (4) error! 答案在这一节的早些时候。(3)和(1)不同,它并不是模板的原型,它只是`Y`的一个偏特化。回顾我们在之前所提到的,偏特化时,模板参数列表并不代表匹配顺序,它们只是为偏特化的模式提供的声明,也就是说,它们的匹配顺序,只是按照``来,而之前的参数只是告诉你`Ts`是一个类型列表,而`U`是一个类型,排名不分先后。 +在这里,我们只提到了变长模板参数的声明,如何使用我们将在第四章讲述。 + ####3.1.3 模板的默认实参 ###3.2 后悔药:SFINAE @@ -1895,7 +1897,7 @@ alexandrescu 关于 min max 的讨论:《再谈Min和Max》 ###7.3 Reactor与Linq ###7.4 Linq的C++实践 ###7.5 Boost.Hana -###7.5 更高更快更强:从Linq到FP +###7.6 更高更快更强:从Linq到FP ## 8 结语:讨论有益,争端无用 From 03ed334db01194f4eff0154549f373bd23c46861 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Fri, 18 Dec 2015 23:04:17 -0800 Subject: [PATCH 45/99] =?UTF-8?q?=E6=9B=B4=E6=96=B04=E7=AB=A0=E4=B9=8B?= =?UTF-8?q?=E5=90=8E=E7=9A=84=E5=86=99=E4=BD=9C=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index d050baa..0b2bc8e 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1876,30 +1876,35 @@ template class Y {}; // (4) error! ###4.2 将循环变成递归,将分支变成递归,将一切变成递归 ###4.3 实战单元:元编程的Fibonacci数列 -## 5 元编程下的算法 +## 5 元编程下的数据结构与算法 ###5.1 列表与数组 ###5.2 字典结构 ###5.3 “快速”排序 +###5.4 其它常用的“轮子” +boost.mpl -## 6 关于模板,你还需要知道的其它常识 -###6.1 类中类:灵活的模板定义 +## 6 模板的进阶技巧 +###6.1 嵌入类 ###6.2 Template-Template Class -###6.3 技巧单元:高阶函数——从函数到函数的组合 -###6.4 实战单元:STL中的Allocator Rebinder -###6.5 更好的编译器,更友善的出错信息 -###6.6 模板的症结:易于实现,难于完美 - -alexandrescu 关于 min max 的讨论:《再谈Min和Max》 +###6.3 高阶函数 +###6.4 闭包:模板的“基于对象” +stl allocator? +mpl::apply +###6.5 占位符(placeholder):在C++中实现方言的基石 +###6.6 编译期“多态” ## 7 模板的威力:从foreach, transform到Linq ###7.1 Foreach与Transform -###7.2 Reactor风格的编程 -###7.3 Reactor与Linq -###7.4 Linq的C++实践 -###7.5 Boost.Hana -###7.6 更高更快更强:从Linq到FP +###7.2 Boost中的模板 +Any Spirit Hana +###7.3 Reactor、Linq与C++中的实践 +###7.4 更高更快更强:从Linq到FP ## 8 结语:讨论有益,争端无用 +###8.1 更好的编译器,更友善的出错信息 +###8.2 模板的症结:易于实现,难于完美 + +alexandrescu 关于 min max 的讨论:《再谈Min和Max》 [1]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf [2]: http://blog.llvm.org/2009/12/dreaded-two-phase-name-lookup.html From 11d56c7e85097f96289a72f06dbe22bb62dc8e9b Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Sun, 20 Dec 2015 19:20:33 -0800 Subject: [PATCH 46/99] Update ReadMe.md --- ReadMe.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index 0b2bc8e..b18cbc1 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1905,7 +1905,10 @@ Any Spirit Hana ###8.2 模板的症结:易于实现,难于完美 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 + [2]: http://blog.llvm.org/2009/12/dreaded-two-phase-name-lookup.html + [3]: https://goo.gl/zCRNYx From 68e384b817d1cb7073b2040c6e04478d5ad66ef9 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Sun, 20 Dec 2015 23:52:22 -0800 Subject: [PATCH 47/99] Update ReadMe.md --- ReadMe.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index b18cbc1..0bfcc5e 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1520,6 +1520,8 @@ error: variable has incomplete type 'A' 符合标准的写法需要将模板类的定义,和模板函数的定义分离开: +> TODO 此处例子不够恰当,并且描述有歧义。需要在未来版本中修订。 + ```C++ struct A; template struct X { From c7193eaf294f72dfbd76169d5dc5e2ca4fe347a9 Mon Sep 17 00:00:00 2001 From: soyli <529140640@qq.com> Date: Tue, 22 Dec 2015 14:24:34 +0800 Subject: [PATCH 48/99] =?UTF-8?q?=E9=94=99=E5=88=AB=E5=AD=97=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 0bfcc5e..1e7175c 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1621,7 +1621,7 @@ template struct X { ### 2.4 本章小结 -这一张是写作中最艰难的一章,中间停滞了将近一年。因为要说清楚C++模板中一些语法噪音和设计决议并不是一件轻松的事情。不过通过这一章的学习,我们知道了下面这几件事情: +这一章是写作中最艰难的一章,中间停滞了将近一年。因为要说清楚C++模板中一些语法噪音和设计决议并不是一件轻松的事情。不过通过这一章的学习,我们知道了下面这几件事情: 1. **部分特化/偏特化** 和 **特化** 相当于是模板实例化过程中的`if-then-else`。这使得我们根据不同类型,选择不同实现的需求得以实现; From e0a7eb81bf034019c7314e08129cdf2b1b9c49e8 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Wed, 23 Dec 2015 15:45:25 -0800 Subject: [PATCH 49/99] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BA=86=E6=9F=90?= =?UTF-8?q?=E4=BA=9B=E8=A1=8C=E6=96=87=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 1e7175c..d5968db 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -53,6 +53,8 @@ C++编译器众多,且对模板的支持可能存在细微差别。如果没 * 需增加: * 模板的使用动机。 + * 增加“如何使用本文”一节。本节将说明全书的体例(强调字体、提示语、例子的组织),所有的描述、举例、引用在重审时将按照体例要求重新组织。 + * 除了用于描述语法的例子外,其他例子将尽量赋予实际意义,以方便阐述意图。 * 建议: * 比较模板和函数的差异性 * 蓝色:C++14 Return type deduction for normal functions 的分析 @@ -1690,7 +1692,7 @@ template class X {}; // ^^^^ 注意这里 ``` -对,就是这个``,跟在X后面的小尾巴,决定了第二条语句是第一条语句的跟班。所以,第二条语句,即“偏特化”,必须要符合原型X的基本形式,那就是只有一个参数。这也是为什么`DoWork`尝试以`template <> struct DoWork`的形式偏特化的时候,编译器会提示参数数量过多。 +对,就是这个``,跟在X后面的“小尾巴”,我们称作实参列表,决定了第二条语句是第一条语句的跟班。所以,第二条语句,即“偏特化”,必须要符合原型X的基本形式:那就是只有一个模板参数。这也是为什么`DoWork`尝试以`template <> struct DoWork`的形式偏特化的时候,编译器会提示模板实参数量过多。 另外一方面,在类模板的实例化阶段,它并不会直接去寻找 `template <> struct DoWork`这个小跟班,而是会先找到基本形式,`template struct DoWork;`,然后再去寻找相应的特化。 @@ -1699,8 +1701,8 @@ template class X {}; ```C++ template struct DoWork; // (0) 这是原型 -template <> struct DoWork {}; // (1) 这是 int 类型的"重载" -template <> struct DoWork {}; // (2) 这是 float 类型的"重载" +template <> struct DoWork {}; // (1) 这是 int 类型的特化 +template <> struct DoWork {}; // (2) 这是 float 类型的特化 DoWork i; // (3) ``` @@ -1716,7 +1718,7 @@ DoWork i; // (3) ```C++ template struct X ; // 0 // 原型有两个类型参数 - // 所以下面的这些偏特化的“小尾巴”(实参列表) + // 所以下面的这些偏特化的实参列表 // 也需要两个类型参数对应 template struct X {}; // 1 template struct X {}; // 2 @@ -1774,9 +1776,9 @@ template struct DoWork; 继而偏特化/特化问题也解决了: ```C++ -template <> struct DoWork {}; // (1) 这是 int 类型的"重载" -template <> struct DoWork {}; // (2) 这是 float 类型的"重载" -template <> struct DoWork {}; // (3) 这是 int, int 类型的“重载” +template <> struct DoWork {}; // (1) 这是 int 类型的特化 +template <> struct DoWork {}; // (2) 这是 float 类型的特化 +template <> struct DoWork {}; // (3) 这是 int, int 类型的特化 ``` 显而易见这个解决方案并不那么完美。首先,不管是偏特化还是用户实例化模板的时候,都需要多撰写好几个`void`,而且最长的那个参数越长,需要写的就越多;其次,如果我们的`DoWork`在程序维护的过程中新加入了一个参数列表更长的实例,那么最悲惨的事情就会发生 —— 原型、每一个偏特化、每一个实例化都要追加上`void`以凑齐新出现的实例所需要的参数数量。 From c038910b36941b4b55df4534193118a7c07302b7 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Wed, 23 Dec 2015 17:13:31 -0800 Subject: [PATCH 50/99] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E9=83=A8?= =?UTF-8?q?=E5=88=86=E4=BD=93=E4=BE=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index d5968db..10ae082 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -49,7 +49,28 @@ C++编译器众多,且对模板的支持可能存在细微差别。如果没 此外,部分复杂实例我们还在文中提供了在线的编译器预览以方便大家阅读和测试。在线编译器参见: http://gcc.godbolt.org/ 。 -###0.5 意见、建议、喷、补遗、写作计划 +###0.5 体例 + +####0.5.1 示例代码 + +```C++ +void SampleCode() { + // 这是一段示例代码 +} +``` + +####0.5.2 引用 + +引用自C++标准: + +> 1.1.2/1 这是一段引用或翻译自标准的文字 + +引用自其他图书: + +> 《书名》 +> 这是一段引用或翻译自其他图书的文字 + +###0.6 意见、建议、喷、补遗、写作计划 * 需增加: * 模板的使用动机。 @@ -1872,6 +1893,8 @@ template class Y {}; // (4) error! ####3.1.3 模板的默认实参 + + ###3.2 后悔药:SFINAE ###3.3 实战单元:获得类型的属性——类型萃取(Type Traits) @@ -1909,6 +1932,7 @@ Any Spirit Hana ###8.2 模板的症结:易于实现,难于完美 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 From d897a5cba3c4ebc26544c601e61f22ec2af83692 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Wed, 23 Dec 2015 18:13:49 -0800 Subject: [PATCH 51/99] =?UTF-8?q?=E6=92=B0=E5=86=993.1.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index 10ae082..8f0125a 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1851,6 +1851,8 @@ void foo(){ ```C++ // Tuple 的声明,来自 boost +struct null_type; + template < class T0 = null_type, class T1 = null_type, class T2 = null_type, class T3 = null_type, class T4 = null_type, class T5 = null_type, @@ -1893,6 +1895,24 @@ template class Y {}; // (4) error! ####3.1.3 模板的默认实参 +在上一节中,我们介绍了模板对默认实参的支持。当时我们的例子很简单,默认模板实参是一个确定的类型`void`或者自定义的`null_type`: + +```C++ +template < + typename T0, + typename T1 = void, + typename T2 = void +> class Tuple; +``` + +实际上,模板的默认参数不仅仅可以是一个确定的类型,它还能是以其他类型为参数的一个类型表达式。考虑下面的例子: + +```C++ +template struct IsFloat { + static bool const value = true; +} + +``` ###3.2 后悔药:SFINAE From 7224463f4fb35f44f3a2cd861986d2ab6fa08d04 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Sun, 3 Jan 2016 00:21:10 -0800 Subject: [PATCH 52/99] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=863.1.3=E7=9A=84?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 8f0125a..682f739 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1905,15 +1905,84 @@ template < > class Tuple; ``` -实际上,模板的默认参数不仅仅可以是一个确定的类型,它还能是以其他类型为参数的一个类型表达式。考虑下面的例子: +实际上,模板的默认参数不仅仅可以是一个确定的类型,它还能是以其他类型为参数的一个类型表达式。 +考虑下面的例子:我们要执行两个同类型变量的除法,它对浮点、整数和其他类型分别采取不同的措施。 +对于浮点,执行内置除法;对于整数,要处理除零保护,防止引发异常;对于其他类型,执行一个叫做`CustomeDiv`的函数。 +第一步,我们先把浮点正确的写出来: ```C++ -template struct IsFloat { - static bool const value = true; +#include + +template T CustomDiv(T lhs, T rhs) { + // Custom Div的实现 } +template ::value> struct SafeDivide { + static T Do(T lhs, T rhs) { + return CustomDiv(lhs, rhs); + } +}; + +template struct SafeDivide{ // 偏特化A + static T Do(T lhs, T rhs){ + return lhs/rhs; + } +}; + +template struct SafeDivide{ // 偏特化B + static T Do(T lhs, T rhs){ + return lhs; + } +}; + +void foo(){ + SafeDivide::Do(1.0f, 2.0f); // 调用偏特化A + SafeDivide::Do(1, 2); // 调用偏特化B +} ``` +在实例化的时候,尽管我们只为`SafeDivide`指定了参数`T`,但是它的另一个参数`IsFloat`在缺省的情况下,可以根据`T`,求出表达式`std::is_floating_point::value`的值作为实参的值,带入到`SafeDivide`的匹配中。 + +嗯,这个时候我们要再把整型和其他类型纳入进来,无外乎就是加这么一个参数: + +```C++ +#include +#include + +template T CustomDiv(T lhs, T rhs) { + T v; + // Custom Div的实现 + return v; +} + +template < + typename T, + bool IsFloat = std::is_floating_point::value, + bool IsIntegral = std::is_integral::value +> struct SafeDivide { + static T Do(T lhs, T rhs) { + return CustomDiv(lhs, rhs); + } +}; + +template struct SafeDivide{ // 偏特化A + static T Do(T lhs, T rhs){ + return lhs/rhs; + } +}; + +template struct SafeDivide{ // 偏特化B + static T Do(T lhs, T rhs){ + return rhs == 0 ? 0 : lhs/rhs; + } +}; + +void foo(){ + SafeDivide::Do(1.0f, 2.0f); // 调用偏特化A + SafeDivide::Do(1, 2); // 调用偏特化B + SafeDivide>::Do({1.f, 2.f}, {1.f, -2.f}); // 调用一般形式 +} +``` ###3.2 后悔药:SFINAE ###3.3 实战单元:获得类型的属性——类型萃取(Type Traits) From 76c4f3f588604bb9eed43bcd38a89aa9cf34ada6 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Sun, 3 Jan 2016 00:24:21 -0800 Subject: [PATCH 53/99] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 682f739..86d926f 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1943,7 +1943,7 @@ void foo(){ 在实例化的时候,尽管我们只为`SafeDivide`指定了参数`T`,但是它的另一个参数`IsFloat`在缺省的情况下,可以根据`T`,求出表达式`std::is_floating_point::value`的值作为实参的值,带入到`SafeDivide`的匹配中。 -嗯,这个时候我们要再把整型和其他类型纳入进来,无外乎就是加这么一个参数: +嗯,这个时候我们要再把整型和其他类型纳入进来,无外乎就是加这么一个参数(http://goo.gl/0Lqywt): ```C++ #include From ee89134cc681288633bdb44694f84dd7206d6e36 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Sun, 3 Jan 2016 00:25:05 -0800 Subject: [PATCH 54/99] Update ReadMe.md --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 86d926f..69fd316 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1943,7 +1943,7 @@ void foo(){ 在实例化的时候,尽管我们只为`SafeDivide`指定了参数`T`,但是它的另一个参数`IsFloat`在缺省的情况下,可以根据`T`,求出表达式`std::is_floating_point::value`的值作为实参的值,带入到`SafeDivide`的匹配中。 -嗯,这个时候我们要再把整型和其他类型纳入进来,无外乎就是加这么一个参数(http://goo.gl/0Lqywt): +嗯,这个时候我们要再把整型和其他类型纳入进来,无外乎就是加这么一个参数( http://goo.gl/0Lqywt ): ```C++ #include From 9302f34d9d9f125c16e212687ebd5842a7a6f5de Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Sun, 3 Jan 2016 15:02:05 -0800 Subject: [PATCH 55/99] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=863.1.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 69fd316..a9ba90e 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1752,6 +1752,7 @@ template struct X {}; // 7 template struct X, shared_ptr>; // 8 // 以下特化,分别对应哪个偏特化的实例? +// 此时偏特化中的T或U分别是什么类型? X v0; X v1; @@ -1764,7 +1765,7 @@ X v7; X v8; ``` -在上面这段例子中,有几个值得注意之处。首先,偏特化时的模板参数,和原型的模板参数没有任何关系,和原型不同,它的顺序完全不影响模式匹配的顺序,它只是偏特化模式,如``中`U`的声明,真正的模式,是由``体现出来的。 +在上面这段例子中,有几个值得注意之处。首先,偏特化时的模板参数,和原型的模板参数没有任何关系。和原型不同,它的顺序完全不影响模式匹配的顺序,它只是偏特化模式,如``中`U`的声明,真正的模式,是由``体现出来的。 这也是为什么在特化的时候,当所有类型都已经确定,我们就可以抛弃全部的模板参数,写出`template <> struct X`这样的形式:因为所有列表中所有参数都确定了,就不需要额外的形式参数了。 @@ -1984,6 +1985,63 @@ void foo(){ } ``` +当然,这时也许你会注意到,`is_integral`,`is_floating_point`和其他类类型三者是互斥的,那能不能只使用一个条件量来进行分派呢?答案当然是可以的( http://goo.gl/jYp5J2 ): + +```C++ +#include +#include + +template T CustomDiv(T lhs, T rhs) { + T v; + // Custom Div的实现 + return v; +} + +template struct SafeDivide { + static T Do(T lhs, T rhs) { + return CustomDiv(lhs, rhs); + } +}; + +template struct SafeDivide< + T, typename std::is_floating_point::type>{ // 偏特化A + static T Do(T lhs, T rhs){ + return lhs/rhs; + } +}; + +template struct SafeDivide< + T, typename std::is_integral::type>{ // 偏特化B + static T Do(T lhs, T rhs){ + return rhs == 0 ? 0 : lhs/rhs; + } +}; + +void foo(){ + SafeDivide::Do(1.0f, 2.0f); // 调用偏特化A + SafeDivide::Do(1, 2); // 调用偏特化B + SafeDivide>::Do({1.f, 2.f}, {1.f, -2.f}); +} +``` + +我们借助这个例子,帮助大家理解一下这个结构是怎么工作的: + +1. 对`SafeDivide` + + * 通过匹配类模板的泛化形式,计算默认实参,可以知道我们要匹配的模板实参是`SafeDivide` + + * 计算两个偏特化的形式的匹配:A得到``,和B得到 `` + + * 最后偏特化B的匹配结果和模板实参一致,使用它。 + +2. 针对`SafeDivide>` + + * 通过匹配类模板的泛化形式,可以知道我们要匹配的模板实参是`SafeDivide, true_type>` + + * 计算两个偏特化形式的匹配:A和B均得到`SafeDivide, false_type>` + + * A和B都与模板实参无法匹配,所以使用原型,调用`CustomDiv` + ###3.2 后悔药:SFINAE ###3.3 实战单元:获得类型的属性——类型萃取(Type Traits) From 4aa1ae1cd76541dacd58a8880b14d61753f30b48 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Mon, 4 Jan 2016 00:43:23 -0800 Subject: [PATCH 56/99] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=863.1.1=E7=A7=8D?= =?UTF-8?q?=E5=AF=B9=E6=A8=A1=E6=9D=BF=E5=81=8F=E7=89=B9=E5=8C=96=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E7=9A=84=E6=8F=8F=E8=BF=B0=E5=92=8C=E4=BE=8B=E5=AD=90?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 53 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index a9ba90e..16c125d 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1720,19 +1720,38 @@ template class X {}; 我们以`DoWork i;`为例,尝试复原一下编译器完成整个模板匹配过程的场景,帮助大家理解。看以下示例代码: ```C++ -template struct DoWork; // (0) 这是原型 +template struct DoWork; // (0) 这是原型 + +template <> struct DoWork {}; // (1) 这是 int 类型的特化 +template <> struct DoWork {}; // (2) 这是 float 类型的特化 +template struct DoWork {}; // (3) 这是指针类型的偏特化 + +DoWork i; // (4) +DoWork pf; // (5) +``` -template <> struct DoWork {}; // (1) 这是 int 类型的特化 -template <> struct DoWork {}; // (2) 这是 float 类型的特化 +1. 编译器分析(0), (1), (2)三句,得知(0)是模板的原型,(1),(2),(3)是模板(0)的特化或偏特化。我们假设有两个字典,第一个字典存储了模板原型,我们称之为`TemplateDict`。第二个字典`TemplateSpecDict`,存储了模板原型所对应的特化/偏特化形式。所以编译器在这几句时,可以视作 -DoWork i; // (3) +```C++ +// 以下为伪代码 +TemplateDict[DoWork] = { + DoWork, + DoWork, + DoWork +}; ``` -1. 编译器分析(0), (1), (2)三句,得知(0)是模板的原型,(1),(2)两句是模板(0)匹配的特例。我们假设有两个字典,第一个字典存储了模板原型,我们称之为`TemplateDict`。第二个字典`TemplateSpecDict`,存储了模板原型所对应的特化/偏特化形式。所以编译器在这三句时,可以视作`TemplateDict.add(DoWork)`,以及 `TemplateSpecDict.get(DoWork).add(int);` 和 `TemplateSpecDict.get(DoWork).add(float);` +2. (4) 试图以`int`实例化类模板`DoWork`。它会在`TemplateDict`中,找到`DoWork`,它有一个形式参数`T`接受类型,正好和我们实例化的要求相符合。并且此时`T`被推导为`int`。(5) 中的`float*`也是同理。 -2. (3) 试图以`int`实例化类模板`DoWork`。它会在`TemplateDict`中,找到`DoWork`,它有一个形式参数`T`接受类型,正好和我们实例化的要求相符合。并且此时`T`被推导为`int`。 +```C++ +// 以下为 DoWork 查找对应匹配的伪代码 +templateProtoInt = TemplateDict.find(DoWork, int); // 查找模板原型,查找到(0) +template = templatePrototype.match(int); // 以 int 对应 int 匹配到 (1) -3. 编译器这个时候就想了,那它会不会有针对`int`的特化呢?于是就去`TemplateSpecDict`中查找,发现果然有`DoWork`的存在,就使用了这个特例。 +// 以下为DoWork 查找对应匹配的伪代码 +templateProtoIntPtr = TemplateDict.find(DoWork, float*) // 查找模板原型,查找到(0) +template = templateProtoIntPtr.match(float*) // 以 float* 对应 U* 匹配到 (3),此时U为float +``` 那么根据上面的步骤所展现的基本原理,我们随便来几个练习: @@ -1765,11 +1784,27 @@ X v7; X v8; ``` -在上面这段例子中,有几个值得注意之处。首先,偏特化时的模板参数,和原型的模板参数没有任何关系。和原型不同,它的顺序完全不影响模式匹配的顺序,它只是偏特化模式,如``中`U`的声明,真正的模式,是由``体现出来的。 +在上面这段例子中,有几个值得注意之处。首先,偏特化时的模板形参,和原型的模板形参没有任何关系。和原型不同,它的顺序完全不影响模式匹配的顺序,它只是偏特化模式,如``中`U`的声明,真正的模式,是由``体现出来的。 这也是为什么在特化的时候,当所有类型都已经确定,我们就可以抛弃全部的模板参数,写出`template <> struct X`这样的形式:因为所有列表中所有参数都确定了,就不需要额外的形式参数了。 -其次,作为一个模式匹配,偏特化的实参列表中展现出来的“样子”,就是它能被匹配的原因。比如,`struct X`中,要求模板的两个参数必须是相同的类型。而`struct X`,则代表第二个模板类型参数必须是第一个模板类型参数的指针,比如`X`就能匹配上。当然,除了简单的指针、`const`和`volatile`修饰符,其他的类模板也可以作为偏特化时的“模式”出现,例如示例8,它要求传入同一个类型的`unique_ptr`和`shared_ptr`。 +其次,作为一个模式匹配,偏特化的实参列表中展现出来的“样子”,就是它能被匹配的原因。比如,`struct X`中,要求模板的两个参数必须是相同的类型。而`struct X`,则代表第二个模板类型参数必须是第一个模板类型参数的指针,比如`X`就能匹配上。当然,除了简单的指针、`const`和`volatile`修饰符,其他的类模板也可以作为偏特化时的“模式”出现,例如示例8,它要求传入同一个类型的`unique_ptr`和`shared_ptr`。C++标准中指出下列模式都是可以被匹配的: + +> N3337, 14.8.2.5/8 + +> 令`T`是模板类型实参或者类型列表(如 _int, float, double_ 这样的,`TT`是template-template实参(参见6.2节),`i`是模板的非类型参数(整数、指针等),则以下形式的形参都会参与匹配: + +> `T`,`cv-list T`,`T*`, `_template-name_ `, `T&`, `T&&` + +>`T [ _integer-constant_ ]` + +>`_type_ (T)`, `T()`, `T(T)` + +>`T _type_ ::*`, `_type_ T::*`, `T T::*` + +>`T (_type_ ::*)()`, `_type_ (T::*)()`, `_type_ (_type_ ::*)(T)`, `_type_ (T::*)(T)`, `T (_type_ ::*)(T)`, `T (T::*)()`, `T (T::*)(T)` + +>`_type_ [i]`, `_template-name_ `, `TT`, `TT`, `TT<>` 对于某些实例化,偏特化的选择并不是唯一的。比如v4的参数是``,能够匹配的就有三条规则,1,6和7。很显然,6还是比7好一些,因为能多匹配一个指针。但是1和6,就很难说清楚谁更好了。一个说明了两者类型相同;另外一个则说明了两者都是指针。所以在这里,编译器也没办法决定使用那个,只好爆出了编译器错误。 From 4b46fcd18b987238e13e1de079374655598ec601 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Mon, 4 Jan 2016 01:05:27 -0800 Subject: [PATCH 57/99] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E9=83=A8?= =?UTF-8?q?=E5=88=86=E4=BB=A3=E7=A0=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 16c125d..c03ec9a 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1842,7 +1842,7 @@ template <> struct DoWork {}; // (3) 这是 int, int 类型的特 所幸模板参数也有一个和函数参数相同的特性:默认实参(Default Arguments)。只需要一个例子,你们就能看明白了(http://goo.gl/TtmcY9): -```C++ +``` C++ template struct DoWork; template struct DoWork {}; @@ -2022,7 +2022,7 @@ void foo(){ 当然,这时也许你会注意到,`is_integral`,`is_floating_point`和其他类类型三者是互斥的,那能不能只使用一个条件量来进行分派呢?答案当然是可以的( http://goo.gl/jYp5J2 ): -```C++ +``` C++ #include #include From c0dc52e4568b683c98f6176e57df15a36f2054a9 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Mon, 4 Jan 2016 01:08:55 -0800 Subject: [PATCH 58/99] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=83=A8=E5=88=86?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=A4=BA=E4=BE=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index c03ec9a..61fd210 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1946,6 +1946,7 @@ template < 对于浮点,执行内置除法;对于整数,要处理除零保护,防止引发异常;对于其他类型,执行一个叫做`CustomeDiv`的函数。 第一步,我们先把浮点正确的写出来: + ```C++ #include @@ -2022,7 +2023,7 @@ void foo(){ 当然,这时也许你会注意到,`is_integral`,`is_floating_point`和其他类类型三者是互斥的,那能不能只使用一个条件量来进行分派呢?答案当然是可以的( http://goo.gl/jYp5J2 ): -``` C++ +```cpp #include #include From 02687cfcf3a076e177d1c8882cf3dd21e7c07530 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Mon, 4 Jan 2016 15:23:32 -0800 Subject: [PATCH 59/99] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BA=863.1.1=E4=B8=AD?= =?UTF-8?q?=E9=83=A8=E5=88=86=E8=A1=8C=E6=96=87=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 61fd210..d86c17d 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1730,7 +1730,7 @@ DoWork i; // (4) DoWork pf; // (5) ``` -1. 编译器分析(0), (1), (2)三句,得知(0)是模板的原型,(1),(2),(3)是模板(0)的特化或偏特化。我们假设有两个字典,第一个字典存储了模板原型,我们称之为`TemplateDict`。第二个字典`TemplateSpecDict`,存储了模板原型所对应的特化/偏特化形式。所以编译器在这几句时,可以视作 +首先,编译器分析(0), (1), (2)三句,得知(0)是模板的原型,(1),(2),(3)是模板(0)的特化或偏特化。我们假设有两个字典,第一个字典存储了模板原型,我们称之为`TemplateDict`。第二个字典`TemplateSpecDict`,存储了模板原型所对应的特化/偏特化形式。所以编译器在这几句时,可以视作 ```C++ // 以下为伪代码 @@ -1741,16 +1741,18 @@ TemplateDict[DoWork] = { }; ``` -2. (4) 试图以`int`实例化类模板`DoWork`。它会在`TemplateDict`中,找到`DoWork`,它有一个形式参数`T`接受类型,正好和我们实例化的要求相符合。并且此时`T`被推导为`int`。(5) 中的`float*`也是同理。 +然后 (4) 试图以`int`实例化类模板`DoWork`。它会在`TemplateDict`中,找到`DoWork`,它有一个形式参数`T`接受类型,正好和我们实例化的要求相符合。并且此时`T`被推导为`int`。(5) 中的`float*`也是同理。 ```C++ -// 以下为 DoWork 查找对应匹配的伪代码 -templateProtoInt = TemplateDict.find(DoWork, int); // 查找模板原型,查找到(0) -template = templatePrototype.match(int); // 以 int 对应 int 匹配到 (1) +{ // 以下为 DoWork 查找对应匹配的伪代码 + templateProtoInt = TemplateDict.find(DoWork, int); // 查找模板原型,查找到(0) + template = templatePrototype.match(int); // 以 int 对应 int 匹配到 (1) +} -// 以下为DoWork 查找对应匹配的伪代码 -templateProtoIntPtr = TemplateDict.find(DoWork, float*) // 查找模板原型,查找到(0) -template = templateProtoIntPtr.match(float*) // 以 float* 对应 U* 匹配到 (3),此时U为float +{ // 以下为DoWork 查找对应匹配的伪代码 + templateProtoIntPtr = TemplateDict.find(DoWork, float*) // 查找模板原型,查找到(0) + template = templateProtoIntPtr.match(float*) // 以 float* 对应 U* 匹配到 (3),此时U为float +} ``` 那么根据上面的步骤所展现的基本原理,我们随便来几个练习: @@ -1794,17 +1796,17 @@ X v8; > 令`T`是模板类型实参或者类型列表(如 _int, float, double_ 这样的,`TT`是template-template实参(参见6.2节),`i`是模板的非类型参数(整数、指针等),则以下形式的形参都会参与匹配: -> `T`,`cv-list T`,`T*`, `_template-name_ `, `T&`, `T&&` +> `T`,`cv-list T`,`T*`, `template-name `, `T&`, `T&&` ->`T [ _integer-constant_ ]` +>`T [ integer-constant ]` ->`_type_ (T)`, `T()`, `T(T)` +>`type (T)`, `T()`, `T(T)` ->`T _type_ ::*`, `_type_ T::*`, `T T::*` +>`T type ::*`, `type T::*`, `T T::*` ->`T (_type_ ::*)()`, `_type_ (T::*)()`, `_type_ (_type_ ::*)(T)`, `_type_ (T::*)(T)`, `T (_type_ ::*)(T)`, `T (T::*)()`, `T (T::*)(T)` +>`T (type ::*)()`, `type (T::*)()`, `type (type ::*)(T)`, `type (T::*)(T)`, `T (type ::*)(T)`, `T (T::*)()`, `T (T::*)(T)` ->`_type_ [i]`, `_template-name_ `, `TT`, `TT`, `TT<>` +>`type [i]`, `template-name `, `TT`, `TT`, `TT<>` 对于某些实例化,偏特化的选择并不是唯一的。比如v4的参数是``,能够匹配的就有三条规则,1,6和7。很显然,6还是比7好一些,因为能多匹配一个指针。但是1和6,就很难说清楚谁更好了。一个说明了两者类型相同;另外一个则说明了两者都是指针。所以在这里,编译器也没办法决定使用那个,只好爆出了编译器错误。 @@ -1935,9 +1937,7 @@ template class Y {}; // (4) error! ```C++ template < - typename T0, - typename T1 = void, - typename T2 = void + typename T0, typename T1 = void, typename T2 = void > class Tuple; ``` From 250c73b42a592b76a7e44aca306d373ea0b50fcb Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Thu, 9 Jun 2016 20:40:28 -0700 Subject: [PATCH 60/99] Update ReadMe.md Added section 'SFINAE' --- ReadMe.md | 460 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 454 insertions(+), 6 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index d86c17d..4f8b684 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -2079,7 +2079,454 @@ void foo(){ * A和B都与模板实参无法匹配,所以使用原型,调用`CustomDiv` ###3.2 后悔药:SFINAE -###3.3 实战单元:获得类型的属性——类型萃取(Type Traits) + +考虑下面这个函数模板: + +``` C++ +template +void foo(T t, typename U::type u) { + // ... +} +``` + +到本节为止,我们所有的例子都保证了一旦咱们敲定了模板参数中 `T` 和 `U`,函数参变量 `t` 和 `u` 的类型都是成立的,比如下面这样: + +``` C++ +struct X { + typedef float type; +}; + +template +void foo(T t, typename U::type u) { + // ... +} + +void callFoo() { + foo(5, 5.0); // T == int, typename U::type == X::type == int +} +``` + +那么这里有一个可能都不算是问题的问题 —— 对于下面的代码,你认为它会提示怎么样的错误: + +```C++ +struct X { + typedef float type; +}; + +struct Y { + typedef float type2; +}; + +template +void foo(T t, typename U::type u) { + // ... +} + +void callFoo() { + foo(5, 5.0); // T == int, typename U::type == X::type == int + foo(5, 5.0); // ??? +} +``` + +这个时候你也许会说:啊,这个简单,`Y` 没有 `type` 这个成员自然会出错啦!嗯,这个时候咱们来看看Clang给出的结果: + +``` +error: no matching function for call to 'foo' + foo(5, 5.0); // ??? + ^~~~~~~~~~~ + note: candidate template ignored: substitution failure [with T = int, U = Y]: no type named 'type' in 'Y' + void foo(T t, typename U::type u) { +``` + +完整翻译过来就是,直接的出错原因是没有匹配的 `foo` 函数,间接原因是尝试用 `[T = int, U = y]` 做类型替换的时候失败了,所以这个函数模板就被忽略了。等等,不是出错,而是被忽略了?那么也就是说,只要有别的能匹配的类型兜着,编译器就无视这里的失败了? + +银河火箭队的阿喵说,就是这样。不信邪的朋友可以试试下面的代码: + +```C++ +struct X { + typedef float type; +}; + +struct Y { + typedef float type2; +}; + +template +void foo(T t, typename U::type u) { + // ... +} + +template +void foo(T t, typename U::type2 u) { + // ... +} +void callFoo() { + foo(5, 5.0); // T == int, typename U::type == X::type == int + foo( 1, 1.0 ); // ??? +} +``` + +这下相信编译器真的是不关心替换失败了吧。我们管这种只要有正确的候选,就无视替换失败的做法为SFINAE。 + +我们不用纠结这个词的发音,它来自于 Substitution failure is not an error 的首字母缩写。这一句之乎者也般难懂的话,由之乎者 —— 啊,不,Substitution,Failure和Error三个词构成。 + +我们从最简单的词“Error”开始理解。Error就是一般意义上的编译错误。一旦出现编译错误,大家都知道,编译器就会中止编译,并且停止接下来的代码生成和链接等后续活动。 + +其次,我们再说“Failure”。很多时候光看字面意思,很多人会把 Failure 和 Error 等同起来。但是实际上Failure很多场合下只是一个中性词。比如我们看下面这个虚构的例子就知道这两者的区别了。 + +假设我们有一个语法分析器,其中某一个规则需要匹配一个token,它可以是标识符,字面量或者是字符串,那么我们会有下面的代码: + +```C++ +switch(token) +{ +case IDENTIFIER: + // do something + break; +case LITERAL_NUMBER: + // do something + break; +case LITERAL_STRING: + // do something + break; +default: + throw WrongToken(token); +} +``` +假如我们当前的token是 `LITERAL_STRING` 的时候,那么第一步它在匹配 `IDENTIFIER` 时,我们可以认为它失败(failure)了,但是它在第三步就会匹配上,所以它并不是一个错误。 + +但是如果这个token既不是标识符、也不是数字字面量、也不是字符串字面量,而且我们的语法规定除了这三类值以外其他统统都是非法的时,我们才认为它是一个error。 + +大家所熟知的函数重载也是如此。比如说下面这个例子: + +```C++ +struct A {}; +struct B: public A {}; +struct C {}; + +void foo(A const&) {} +void foo(B const&) {} + +void callFoo() { + foo( A() ); + foo( B() ); + foo( C() ); +} +``` + +那么 `foo( A() )` 虽然匹配 `foo(B const&)` 会失败,但是它起码能匹配 `foo(A const&)`,所以它是正确的;`foo( B() )` 能同时匹配两个函数原型,但是 `foo(B const&)` 要更好一些,因此它选择了这个原型。而 `foo( C() );` 因为两个函数都匹配失败(Failure)了,所以它找不到相应的原型,这时才会爆出一个编译器错误(Error)。 + +所以到这里我们就明白了,在很多情况下,Failure is not an error。编译器在遇到Failure的时候,往往还需要尝试其他的可能性。 + +好,现在我们把最后一个词,Substitution,加入到我们的字典中。现在这句话的意思就是说,我们要把 Failure is not an error 的概念,推广到Substitution阶段。 + +所谓substitution,就是将函数模板中的形参,替换成实参的过程。概念很简洁但是实现却颇多细节,所以C++标准中对这一概念的解释比较拗口。它分别指出了以下几点: + + * 什么时候函数模板会发生实参 替代(Substitute) 形参的行为; + + * 什么样的行为被称作 Substitution; + + * 什么样的行为不可以被称作 Substitution Failure —— 他们叫SFINAE error。 + +我们在此不再详述,有兴趣的同学可以参照 http://en.cppreference.com/w/cpp/language/sfinae ,这是标准的一个精炼版本。这里我们简单的解释一下。 + +考虑我们有这么个函数签名: + +```C++ +template < + typename T0, + // 一大坨其他模板参数 + typename U = /* 和前面T有关的一大坨 */ +> +RType /* 和模板参数有关的一大坨 */ +functionName ( + PType0 /* PType0 是和模板参数有关的一大坨 */, + PType1 /* PType1 是和模板参数有关的一大坨 */, + // ... 其他参数 +) { + // 实现,和模板参数有关的一大坨 +} +``` + +那么,在这个函数模板被实例化的时候,所有函数签名上的“和模板参数有关的一大坨”被推导出具体类型的过程,就是替换。一个更具体的例子来解释上面的“一大坨”: + +```C++ +template < + typename T, + typenname U = typename vector::iterator // 1 +> +typename vector::value_type // 1 + foo( + T*, // 1 + T&, // 1 + typename T::internal_type, // 1 + typename add_reference::type, // 1 + int // 这里都不需要 substitution + ) +{ + // 整个实现部分,都没有 substitution。这个很关键。 +} +``` + +所有标记为 `1` 的部分,都是需要替换的部分,而它们在替换过程中的失败(failure),就称之为替换失败(substitution failure)。 + +下面的代码是提供了一些替换成功和替换失败的示例: + +```C++ +struct X { + typedef int type; +}; + +struct Y { + typedef int type2; +}; + +template void foo(typename T::type); // Foo0 +template void foo(typename T::type2); // Foo1 +template void foo(T); // Foo2 + +void callFoo() { + foo(5); // Foo0: Succeed, Foo1: Failed, Foo2: Failed + foo(10); // Foo0: Failed, Foo1: Succeed, Foo2: Failed + foo(15); // Foo0: Failed, Foo1: Failed, Foo2: Succeed +} +``` + +在这个例子中,当我们指定 `foo` 的时候,substitution就开始工作了,而且会同时工作在三个不同的 `foo` 签名上。如果我们仅仅因为 `Y` 没有 `type`,匹配 `Foo0` 失败了,就宣布代码有错,中止编译,那显然是武断的。因为 `Foo1` 是可以被正确替换的,我们也希望 `Foo1` 成为 `foo` 的原型。 + +std/boost库中的 `enable_if` 是 SFINAE 最直接也是最主要的应用。所以我们通过下面 `enable_if` 的例子,来深入理解一下 SFINAE 在模板编程中的作用。 + +假设我们有两个不同类型的计数器(counter),一种是普通的整数类型,另外一种是一个复杂对象,它从接口 `ICounter` 继承,这个接口有一个成员叫做increase实现计数功能。现在,我们想把这两种类型的counter封装一个统一的调用:inc_counter。那么,我们直觉会简单粗暴的写出下面的代码: + +```C++ +struct ICounter { + virtual void increase() = 0; + virtual ~ICounter() {} +}; + +struct Counter: public ICounter { + void increase() override { + // Implements + } +}; + +template +void inc_counter(T& counterObj) { + counterObj.increase(); +} + +template +void inc_counter(T& intTypeCounter){ + ++intTypeCounter; +} + +void doSomething() { + Counter cntObj; + uint32_t cntUI32; + + // blah blah blah + inc_counter(cntObj); + inc_counter(cntUI32); +} +``` + +我们非常希望它展现出预期的行为。因为其实我们是知道对于任何一个调用,两个 `inc_counter` 只有一个是能够编译正确的。“有且唯一”,我们理应当期望编译器能够挑出那个唯一来。 + +可惜编译器做不到这一点。首先,它就告诉我们,这两个签名 + +```C++ +template void inc_counter(T& counterObj); +template void inc_counter(T& intTypeCounter); +``` + +其实是一模一样的。我们遇到了 `redefinition`。 + +我们看看 `enable_if` 是怎么解决这个问题的。我们通过 `enable_if` 这个 `T` 对于不同的实例做个限定: + +```C++ +template void inc_counter( + T& counterObj, + typename std::enable_if< + is_base_of::value + >::type* = nullptr ); + +template void inc_counter( + T& counterInt, + typename std::enable_if< + std::is_integral::value + >::type* = nullptr ); +``` + +然后我们解释一下,这个 `enable_if` 是怎么工作的,语法为什么这么丑: + +首先,替换(substitution)只有在推断函数类型的时候,才会起作用。推断函数类型需要参数的类型,所以, `typename std::enable_if::value>::type` 这么一长串代码,就是为了让 `enable_if` 参与到函数类型中; + +其次, `is_integral::value` 返回一个布尔类型的编译器常数,告诉我们它是或者不是一个 `integral type`,`enable_if` 的作用就是,如果这个 `C` 值为 `True`,那么 `enable_if::type` 就会被推断成一个 `void` 或者是别的什么类型,让整个函数匹配后的类型变成 `void inc_counter(int & counterInt, void* dummy = nullptr);` 如果这个值为 `False` ,那么 `enable_if` 这个特化形式中,压根就没有这个 `::type`,于是替换就失败了。和我们之前的例子中一样,这个函数原型就不会被产生出来。 + +所以我们能保证,无论对于 `int` 还是 `counter` 类型的实例,我们都只有一个函数原型通过了substitution —— 这样就保证了它的“有且唯一”,编译器也不会因为你某个替换失败而无视成功的那个实例。 + +这个例子说到了这里,熟悉C++的你,一定会站出来说我们只要把第一个签名改成: + +```C++ +void inc_counter(ICounter& counterObj); +``` + +就能完美解决这个问题了,根本不需要这么复杂的编译器机制。 + +嗯,你说的没错,在这里这个特性一点都没用。 + +这也提醒我们,当你觉得需要写 `enable_if` 的时候,首先要考虑到以下可能性: + + * 重载(对模板函数) + + * 偏特化(对模板类而言) + + * 虚函数 + + +但是问题到了这里并没有结束。因为 `increase` 毕竟是个虚函数。假如 `Counter` 需要调用的地方实在是太多了,这个时候我们会非常期望 `increase` 不再是个虚函数以提高性能。此时我们会调整继承层级: + +```C++ +struct ICounter {}; +struct Counter: public ICounter { + void increase() { + // impl + } +}; +``` + +那么原有的 `void inc_counter(ICounter& counterObj)` 就无法再执行下去了。这个时候你可能会考虑一些变通的办法: + +```C++ +template +void inc_counter(ICounter& c) {}; + +template +void inc_counter(T& c) { ++c; }; + +void doSomething() { + Counter cntObj; + uint32_t cntUI32; + + // blah blah blah + inc_counter(cntObj); // 1 + inc_counter(static_cast(cntObj)); // 2 + inc_counter(cntUI32); // 3 +} +``` + +对于调用 `1`,因为 `cntObj` 到 `ICounter` 是需要类型转换的,所以比 `void inc_counter(T&) [T = Counter]` 要更差一些。然后它会直接实例化后者,结果实现变成了 `++cntObj`,BOOM! + +那么我们做 `2` 试试看?嗯,工作的很好。但是等等,我们的初衷是什么来着?不就是让 `inc_counter` 对不同的计数器类型透明吗?这不是又一夜回到解放前了? + +所以这个时候,就能看到 `enable_if` 是如何通过 SFINAE 发挥威力的了: + +```C++ +#include +#include +#include + +struct ICounter {}; +struct Counter: public ICounter { + void increase() { + // impl + } +}; + +template void inc_counter( + T& counterObj, + typename std::enable_if< + std::is_base_of::value + >::type* = nullptr ){ + counterObj.increase(); +} + +template void inc_counter( + T& counterInt, + typename std::enable_if< + std::is_integral::value + >::type* = nullptr ){ + ++counterInt; +} + +void doSomething() { + Counter cntObj; + uint32_t cntUI32; + + // blah blah blah + inc_counter(cntObj); // OK! + inc_counter(cntUI32); // OK! +} +``` + +这个代码是不是看起来有点脏脏的。眼尖的你定睛一瞧,咦, `ICounter` 不是已经空了吗,为什么我们还要用它作为基类呢? + +这是个好问题。在本例中,我们用它来区分一个`counter`是不是继承自`ICounter`。最终目的,是希望知道 `counter` 有没有 `increase` 这个函数。 + +所以 `ICounter` 只是相当于一个标签。而于情于理这个标签都是个累赘。但是在C++11之前,我们并没有办法去写类似于: + +```C++ +template void foo(T& c, decltype(c.increase())* = nullptr); +``` + +这样的函数签名,因为假如 `T` 是 `int`,那么 `c.increase()` 这个函数调用就不存在。但它又不属于Type Failure,而是一个Expression Failure,在C++11之前它会直接导致编译器出错,这并不是我们所期望的。所以我们才退而求其次,用一个类似于标签的形式来提供我们所需要的类型信息。以后的章节,后面我们会说到,这种和类型有关的信息我们可以称之为 `type traits`。 + +到了C++11,它正式提供了 Expression SFINAE,这时我们就能抛开 `ICounter` 这个无用的Tag,直接写出我们要写的东西: + +```C++ +struct Counter { + void increase() { + // Implements + } +}; + +template +void inc_counter(T& intTypeCounter, std::decay_t* = nullptr) { + ++intTypeCounter; +} + +template +void inc_counter(T& counterObj, std::decay_t* = nullptr) { + counterObj.increase(); +} + +void doSomething() { + Counter cntObj; + uint32_t cntUI32; + + // blah blah blah + inc_counter(cntObj); + inc_counter(cntUI32); +} +``` + +此外,还有一种情况只能使用 SFINAE,而无法使用包括继承、重载在内的任何方法,这就是Universal Reference。比如, + +```C++ +// 这里的a是个通用引用,可以准确的处理左右值引用的问题。 +template void foo(ArgT&& a); +``` + +加入我们要限定ArgT只能是 float 的衍生类型,那么写成下面这个样子是不对的,它实际上只能接受 float 的右值引用。 + +```C++ +void foo(float&& a); +``` + +此时的唯一选择,就是使用Universal Reference,并增加 `enable_if` 限定类型,如下面这样: + +```C++ +template +void foo( + ArgT&& a, + typename std::enabled_if< + is_same::value + >::type* = nullptr +); +``` + +从上面这些例子可以看到,SFINAE最主要的作用,是保证编译器在泛型函数、偏特化、及一般重载函数中遴选函数原型的候选列表时不被打断。除此之外,它还有一个很重要的元编程作用就是实现部分的编译期自省和反射。 + +虽然它写起来并不直观,但是对于既没有编译器自省、也没有Concept的C++1y来说,已经是最好的选择了。 ## 4 用模板写程序吧!骚年! ###4.1 模板上的递归 @@ -2087,11 +2534,12 @@ void foo(){ ###4.3 实战单元:元编程的Fibonacci数列 ## 5 元编程下的数据结构与算法 -###5.1 列表与数组 -###5.2 字典结构 -###5.3 “快速”排序 -###5.4 其它常用的“轮子” -boost.mpl +###5.1 获得类型的属性——类型萃取(Type Traits) +###5.2 列表与数组 +###5.3 字典结构 +###5.4 “快速”排序 +###5.5 其它常用的“轮子” +boost.hana ## 6 模板的进阶技巧 ###6.1 嵌入类 From 45c001f8a3810948c5690759e26a91e465be8b7c Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Tue, 14 Jun 2016 12:19:26 -0700 Subject: [PATCH 61/99] Fixed #14. --- ReadMe.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 4f8b684..9a5aded 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1469,8 +1469,8 @@ template struct Y }; void poo(){ - X::foo(); - X::foo(); + Y::foo(); + Y::foo(); } ``` From c4f8640f632c00703161686cd735a8a3461313ed Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Tue, 14 Jun 2016 12:22:43 -0700 Subject: [PATCH 62/99] Fixed #19 --- ReadMe.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 9a5aded..14d0e7f 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -729,7 +729,7 @@ Variant addFloatOrMulInt(Variant const* a, Variant const* b) 更常见的是 `void*`: ``` C++ -#define BIN_OP(type, a, op, b, result) (*(type const *)(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) @@ -1030,9 +1030,9 @@ void PrintID() // TypeToID 的模板“原型” // ... -template class TypeToID +template <> class TypeToID { - static int const ID = 0xF10A7; + static int const ID = 0xF10A7; }; ``` @@ -1041,12 +1041,12 @@ template class TypeToID ``` C++ template <> class TypeToID { - static int const ID = 0x401d; + static int const ID = 0x401d; }; void PrintID() { - cout << "ID of uint8_t: " << TypeToID::ID << endl; + cout << "ID of uint8_t: " << TypeToID::ID << endl; } ``` From ea1e6ef7675ee573ffa47d50c645b752b8a093fe Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Tue, 14 Jun 2016 12:27:57 -0700 Subject: [PATCH 63/99] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BA=86=E7=AB=A0?= =?UTF-8?q?=E8=8A=82=E5=AE=89=E6=8E=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 50 ++++++++++++++++++++++---------------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 14d0e7f..0890ab9 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -2528,42 +2528,36 @@ void foo( 虽然它写起来并不直观,但是对于既没有编译器自省、也没有Concept的C++1y来说,已经是最好的选择了。 -## 4 用模板写程序吧!骚年! -###4.1 模板上的递归 -###4.2 将循环变成递归,将分支变成递归,将一切变成递归 -###4.3 实战单元:元编程的Fibonacci数列 - -## 5 元编程下的数据结构与算法 -###5.1 获得类型的属性——类型萃取(Type Traits) -###5.2 列表与数组 -###5.3 字典结构 -###5.4 “快速”排序 -###5.5 其它常用的“轮子” +## 4 元编程下的数据结构与算法 +###4.1 表达式与数值计算 +###4.1 获得类型的属性——类型萃取(Type Traits) +###4.2 列表与数组 +###4.3 字典结构 +###4.4 “快速”排序 +###4.5 其它常用的“轮子” boost.hana -## 6 模板的进阶技巧 -###6.1 嵌入类 -###6.2 Template-Template Class -###6.3 高阶函数 -###6.4 闭包:模板的“基于对象” +## 5 模板的进阶技巧 +###5.1 嵌入类 +###5.2 Template-Template Class +###5.3 高阶函数 +###5.4 闭包:模板的“基于对象” stl allocator? mpl::apply -###6.5 占位符(placeholder):在C++中实现方言的基石 -###6.6 编译期“多态” +###5.5 占位符(placeholder):在C++中实现方言的基石 +###5.6 编译期“多态” -## 7 模板的威力:从foreach, transform到Linq -###7.1 Foreach与Transform -###7.2 Boost中的模板 +## 6 模板的威力:从foreach, transform到Linq +###6.1 Foreach与Transform +###6.2 Boost中的模板 Any Spirit Hana -###7.3 Reactor、Linq与C++中的实践 -###7.4 更高更快更强:从Linq到FP - -## 8 结语:讨论有益,争端无用 -###8.1 更好的编译器,更友善的出错信息 -###8.2 模板的症结:易于实现,难于完美 +###6.3 Reactor、Linq与C++中的实践 +###6.4 更高更快更强:从Linq到FP +## 7 结语:讨论有益,争端无用 +###7.1 更好的编译器,更友善的出错信息 +###7.2 模板的症结:易于实现,难于完美 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 From 6ae510190719e61906eb66d4d89cb86663975c8b Mon Sep 17 00:00:00 2001 From: Ye WU Date: Wed, 15 Jun 2016 01:16:41 -0700 Subject: [PATCH 64/99] Added quicksort as candidate sample to code list. --- QuickSort.cpp | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 QuickSort.cpp diff --git a/QuickSort.cpp b/QuickSort.cpp new file mode 100644 index 0000000..eefa00b --- /dev/null +++ b/QuickSort.cpp @@ -0,0 +1,104 @@ +#include +#include +#include + +using std::tuple; +using std::integral_constant; +using std::is_same; + +template struct to_int_types{ + typedef tuple< integral_constant... > type; +}; + +template struct Less { + template struct Apply { + static bool const value = X::value < Pivot::value; + }; +}; + +template struct GE { + template struct Apply { + static bool const value = X::value >= Pivot::value; + }; +}; + +template struct Tuple_PushFront {}; +template struct Tuple_PushFront> { + typedef tuple type; +}; +template struct Tuple_PushFront> { + typedef tuple type; +}; + +template struct Filter; +template struct Filter< Pred, tuple<> > { + typedef tuple<> type; +}; +template struct Filter< Pred, tuple > { + typedef typename Tuple_PushFront< + Head, Pred::template Apply::value, typename Filter>::type + >::type type; +}; + +template struct ConcatenateTuple {}; +template struct ConcatenateTuple { + typedef Tuple0 type; +}; + +template struct ConcatenateTuple { + template struct ConcatenateImpl {}; + template struct ConcatenateImpl< tuple > { + template struct Apply; + template struct Apply< tuple > { + typedef tuple type; + }; + }; + typedef typename ConcatenateImpl::template Apply::type type; +}; +template struct ConcatenateTuple { + typedef typename ConcatenateTuple< + FirstTuple, typename ConcatenateTuple::type + >::type type; +}; + +template struct QuickSort {}; +template <> struct QuickSort< tuple< > > { typedef tuple< > type; }; +template struct QuickSort< tuple > { typedef tuple type; }; +template +struct QuickSort< tuple > { + typedef typename Filter< Less, tuple >::type LeftElems; + typedef typename Filter< GE , tuple >::type RightElems; + typedef typename ConcatenateTuple< + typename QuickSort::type, tuple, typename QuickSort::type + >::type type; +}; + +void StaticTest() +{ + typedef to_int_types<1, 2, 3>::type lst_1_3; + typedef to_int_types<3, 2, 1>::type lst_3_1; + typedef to_int_types<3, 7, 1, 6, 5, 22, 5>::type lst; + + typedef integral_constant::type i1; + typedef integral_constant::type i2; + typedef integral_constant::type i3; + + static_assert(Less::Apply::value == true, ""); + static_assert(Less::Apply::value == false, ""); + static_assert(Less::Apply::value == false, ""); + + static_assert(GE::Apply::value == true, ""); + static_assert(GE::Apply::value == true, ""); + static_assert(GE::Apply::value == false, ""); + + static_assert(is_same>::type, tuple>::value, ""); + static_assert(is_same>::type, tuple>::value, ""); + static_assert(is_same>::type, tuple>::value, ""); + + static_assert(is_same, lst_1_3>::type, tuple>::value, ""); + + static_assert(is_same::type, lst_1_3>::value, ""); + static_assert(is_same::type, lst_1_3>::value, ""); + static_assert(is_same >::type, tuple<>>::value, ""); + static_assert(is_same>::type, tuple>::value, ""); +} \ No newline at end of file From c3c489732945e036983572f9c0b99d9e9c63d62f Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Tue, 21 Jun 2016 16:56:43 -0700 Subject: [PATCH 65/99] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BA=86enable=5Fif?= =?UTF-8?q?=E4=B8=ADUniversal=20Reference=E7=9A=84=E4=BE=8B=E5=AD=90?= =?UTF-8?q?=E4=B8=AD=E7=9A=84bug=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 0890ab9..2d14185 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -2519,7 +2519,7 @@ template void foo( ArgT&& a, typename std::enabled_if< - is_same::value + is_same, float>::value >::type* = nullptr ); ``` From afb078742e19e790c3555c07eee3d8aa59047779 Mon Sep 17 00:00:00 2001 From: freezestudio Date: Thu, 23 Jun 2016 18:41:31 +0800 Subject: [PATCH 66/99] Update ReadMe.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重新修正http链接错误 --- ReadMe.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 2d14185..3d1e0a5 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,4 +1,4 @@ - + # C++ Template 进阶指南 ## 0. 前言 @@ -47,7 +47,7 @@ C++编译器众多,且对模板的支持可能存在细微差别。如果没 * Visual Studio 2015 * GCC 4.9.2 (x86) -此外,部分复杂实例我们还在文中提供了在线的编译器预览以方便大家阅读和测试。在线编译器参见: http://gcc.godbolt.org/ 。 +此外,部分复杂实例我们还在文中提供了在线的编译器预览以方便大家阅读和测试。在线编译器参见: [`gcc.godbolt.org`](http://gcc.godbolt.org/)。 ###0.5 体例 @@ -1694,7 +1694,7 @@ void f(){ } ``` -这个例子在字面上“看起来”并没有什么问题,可惜编译器在编译的时候仍然提示出错了(http://goo.gl/zI42Zv): +这个例子在字面上“看起来”并没有什么问题,可惜编译器在编译的时候仍然提示出错了[`goo.gl/zI42Zv`](http://goo.gl/zI42Zv): ``` 5 : error: too many template arguments for class template 'DoWork' @@ -1810,7 +1810,7 @@ X v8; 对于某些实例化,偏特化的选择并不是唯一的。比如v4的参数是``,能够匹配的就有三条规则,1,6和7。很显然,6还是比7好一些,因为能多匹配一个指针。但是1和6,就很难说清楚谁更好了。一个说明了两者类型相同;另外一个则说明了两者都是指针。所以在这里,编译器也没办法决定使用那个,只好爆出了编译器错误。 -其他的示例可以先自己推测一下, 再去编译器上尝试一番 (http://goo.gl/9UVzje)。 +其他的示例可以先自己推测一下, 再去编译器上尝试一番:[`goo.gl/9UVzje`](http://goo.gl/9UVzje)。 #### 3.1.2 不定长的模板参数 @@ -1842,7 +1842,7 @@ template <> struct DoWork {}; // (3) 这是 int, int 类型的特 显而易见这个解决方案并不那么完美。首先,不管是偏特化还是用户实例化模板的时候,都需要多撰写好几个`void`,而且最长的那个参数越长,需要写的就越多;其次,如果我们的`DoWork`在程序维护的过程中新加入了一个参数列表更长的实例,那么最悲惨的事情就会发生 —— 原型、每一个偏特化、每一个实例化都要追加上`void`以凑齐新出现的实例所需要的参数数量。 -所幸模板参数也有一个和函数参数相同的特性:默认实参(Default Arguments)。只需要一个例子,你们就能看明白了(http://goo.gl/TtmcY9): +所幸模板参数也有一个和函数参数相同的特性:默认实参(Default Arguments)。只需要一个例子,你们就能看明白了[`goo.gl/TtmcY9`](http://goo.gl/TtmcY9): ``` C++ template struct DoWork; @@ -1885,7 +1885,7 @@ void foo(){ 不过不管怎么说,以长参数加默认参数的方式支持变长参数是可行的做法,这也是C++98/03时代的唯一选择。 -例如,[Boost.Tuple](https://github.com/boostorg/tuple/blob/develop/include/boost/tuple/detail/tuple_basic.hpp)就使用了这个方法,支持了变长的Tuple: +例如,[`Boost.Tuple`](https://github.com/boostorg/tuple/blob/develop/include/boost/tuple/detail/tuple_basic.hpp)就使用了这个方法,支持了变长的Tuple: ```C++ // Tuple 的声明,来自 boost @@ -1980,7 +1980,7 @@ void foo(){ 在实例化的时候,尽管我们只为`SafeDivide`指定了参数`T`,但是它的另一个参数`IsFloat`在缺省的情况下,可以根据`T`,求出表达式`std::is_floating_point::value`的值作为实参的值,带入到`SafeDivide`的匹配中。 -嗯,这个时候我们要再把整型和其他类型纳入进来,无外乎就是加这么一个参数( http://goo.gl/0Lqywt ): +嗯,这个时候我们要再把整型和其他类型纳入进来,无外乎就是加这么一个参数[`goo.gl/0Lqywt`](http://goo.gl/0Lqywt): ```C++ #include @@ -2021,7 +2021,7 @@ void foo(){ } ``` -当然,这时也许你会注意到,`is_integral`,`is_floating_point`和其他类类型三者是互斥的,那能不能只使用一个条件量来进行分派呢?答案当然是可以的( http://goo.gl/jYp5J2 ): +当然,这时也许你会注意到,`is_integral`,`is_floating_point`和其他类类型三者是互斥的,那能不能只使用一个条件量来进行分派呢?答案当然是可以的:[`goo.gl/jYp5J2`](http://goo.gl/jYp5J2): ```cpp #include @@ -2227,7 +2227,7 @@ void callFoo() { * 什么样的行为不可以被称作 Substitution Failure —— 他们叫SFINAE error。 -我们在此不再详述,有兴趣的同学可以参照 http://en.cppreference.com/w/cpp/language/sfinae ,这是标准的一个精炼版本。这里我们简单的解释一下。 +我们在此不再详述,有兴趣的同学可以参照[`这里`](http://en.cppreference.com/w/cpp/language/sfinae),这是标准的一个精炼版本。这里我们简单的解释一下。 考虑我们有这么个函数签名: From eeb131bc83e54be59aa285a21bc231c7aca8eed6 Mon Sep 17 00:00:00 2001 From: freezestudio Date: Sun, 10 Jul 2016 08:42:31 +0800 Subject: [PATCH 67/99] Update QuickSort.cpp --- QuickSort.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/QuickSort.cpp b/QuickSort.cpp index eefa00b..156dd8d 100644 --- a/QuickSort.cpp +++ b/QuickSort.cpp @@ -78,6 +78,7 @@ void StaticTest() typedef to_int_types<1, 2, 3>::type lst_1_3; typedef to_int_types<3, 2, 1>::type lst_3_1; typedef to_int_types<3, 7, 1, 6, 5, 22, 5>::type lst; + typedef to_int_types<1, 3, 5, 5, 6, 7, 22>::type sorted_lst; typedef integral_constant::type i1; typedef integral_constant::type i2; @@ -101,4 +102,5 @@ void StaticTest() static_assert(is_same::type, lst_1_3>::value, ""); static_assert(is_same >::type, tuple<>>::value, ""); static_assert(is_same>::type, tuple>::value, ""); -} \ No newline at end of file + static_assert(is_same::type, sorted_lst>::value, ""); +} From ee0702cfefcc17e49077bae0e2e61e4a7047ea73 Mon Sep 17 00:00:00 2001 From: Ye WU Date: Thu, 27 Oct 2016 02:48:58 -0700 Subject: [PATCH 68/99] Added some new topics. --- ReadMe.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 3d1e0a5..456fa1b 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -44,10 +44,16 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言, C++编译器众多,且对模板的支持可能存在细微差别。如果没有特别强调,本书行文过程中,使用了下列编译器来测试文中提供的代码和示例: * Clang 3.7 (x86) -* Visual Studio 2015 -* GCC 4.9.2 (x86) +* Visual Studio 2015 Update 3 +* GCC 7 (x86, snapshot) -此外,部分复杂实例我们还在文中提供了在线的编译器预览以方便大家阅读和测试。在线编译器参见: [`gcc.godbolt.org`](http://gcc.godbolt.org/)。 +此外,部分复杂实例我们还在文中提供了在线的编译器预览以方便大家阅读和测试。在线编译器参见: [`gcc.godbolt.org`](https://gcc.godbolt.org/)。 + +一些示例中用到的特性所对应的C++标准: + +|特性|标准| +|---|---| +| std::decay_t | C++ 14 | ###0.5 体例 @@ -2420,6 +2426,7 @@ void doSomething() { 所以这个时候,就能看到 `enable_if` 是如何通过 SFINAE 发挥威力的了: + ```C++ #include #include @@ -2528,6 +2535,8 @@ void foo( 虽然它写起来并不直观,但是对于既没有编译器自省、也没有Concept的C++1y来说,已经是最好的选择了。 +(补充例子:构造函数上的enable_if) + ## 4 元编程下的数据结构与算法 ###4.1 表达式与数值计算 ###4.1 获得类型的属性——类型萃取(Type Traits) @@ -2535,7 +2544,6 @@ void foo( ###4.3 字典结构 ###4.4 “快速”排序 ###4.5 其它常用的“轮子” -boost.hana ## 5 模板的进阶技巧 ###5.1 嵌入类 @@ -2550,13 +2558,14 @@ mpl::apply ## 6 模板的威力:从foreach, transform到Linq ###6.1 Foreach与Transform ###6.2 Boost中的模板 -Any Spirit Hana +Any Spirit Hana TypeErasure ###6.3 Reactor、Linq与C++中的实践 ###6.4 更高更快更强:从Linq到FP ## 7 结语:讨论有益,争端无用 ###7.1 更好的编译器,更友善的出错信息 ###7.2 模板的症结:易于实现,难于完美 +###7.3 一些期望 alexandrescu 关于 min max 的讨论:《再谈Min和Max》 std::experimental::any / boost.any 对于 reference 的处理 From b0ae2e73e2530f723ba2184c9a6539a638fecfbc Mon Sep 17 00:00:00 2001 From: YIN LIU Date: Tue, 21 Mar 2017 17:33:45 +0800 Subject: [PATCH 69/99] Fixed the format to meet the Github markdown format requirements. --- ReadMe.md | 513 +++++++++++++++++++++++++++--------------------------- 1 file changed, 254 insertions(+), 259 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 456fa1b..f239101 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,9 +1,9 @@ -# C++ Template 进阶指南 +# C++ Template 进阶指南 ## 0. 前言 -###0.1 C++另类简介:比你用的复杂,但比你想的简单 +### 0.1 C++另类简介:比你用的复杂,但比你想的简单 C++似乎从他为世人所知的那天开始便成为天然的话题性编程语言。在它在周围有着形形色色的赞美与贬低之词。当我在微博上透露欲写此文的意愿时,也收到了很多褒贬不一的评论。作为一门语言,能拥有这么多使用并恨着它、使用并畏惧它的用户,也算是语言丛林里的奇观了。 @@ -21,7 +21,7 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言, 本文的写作初衷,就是通过“编程语言”的视角,介绍一个简单、清晰的“模板语言”。我会尽可能的将模板的诸多要素连串起来,用一些简单的例子帮助读者学习这门“语言”,让读者在编写、阅读模板代码的时候,能像 `if(exp) { dosomething(); }`一样的信手拈来,让“模板元编程”技术成为读者牢固掌握、可举一反三的有用技能。 -###0.2 适宜读者群 +### 0.2 适宜读者群 因为本文并不是用于C++入门,例子中也多少会牵涉一些其它知识,因此如果读者能够具备以下条件,会读起来更加轻松: @@ -33,13 +33,13 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言, 诚如上节所述,本文并不是《C++ Templates》的简单重复,与《Modern C++ Design》交叠更少。从知识结构上,我建议大家可以先读本文,再阅读《C++ Templates》获取更丰富的语法与实现细节,以更进一步;《Modern C++ Design》除了元编程之外,还有很多的泛型编程示例,原则上泛型编程的部分与我所述的内容交叉不大,读者在读完1-3章了解模板的基本规则之后便可阅读《MCD》的相应章节;元编程部分(如Typelist)建议在阅读完本文之后再行阅读,或许会更易理解。 -###0.3 版权 +### 0.3 版权 本文是随写随即同步到Github上,因此在行文中难免会遗漏引用。本文绝大部分内容应是直接承出我笔,但是也不定会有他山之石。所有指涉内容我会尽量以引号框记,或在上下文和边角注记中标示,如有遗漏烦请不吝指出。 全文所有为我所撰写的部分,作者均保留所有版权。如果有需要转帖或引用,还请注明出处并告知于我。 -###0.4 推荐编译环境 +### 0.4 推荐编译环境 C++编译器众多,且对模板的支持可能存在细微差别。如果没有特别强调,本书行文过程中,使用了下列编译器来测试文中提供的代码和示例: @@ -55,9 +55,9 @@ C++编译器众多,且对模板的支持可能存在细微差别。如果没 |---|---| | std::decay_t | C++ 14 | -###0.5 体例 +### 0.5 体例 -####0.5.1 示例代码 +#### 0.5.1 示例代码 ```C++ void SampleCode() { @@ -65,7 +65,7 @@ void SampleCode() { } ``` -####0.5.2 引用 +#### 0.5.2 引用 引用自C++标准: @@ -76,7 +76,7 @@ void SampleCode() { > 《书名》 > 这是一段引用或翻译自其他图书的文字 -###0.6 意见、建议、喷、补遗、写作计划 +### 0.6 意见、建议、喷、补遗、写作计划 * 需增加: * 模板的使用动机。 @@ -88,9 +88,9 @@ void SampleCode() { ## 1. Template的基本语法 -###1.1 Template Class基本语法 +### 1.1 Template Class基本语法 -####1.1.1 Template Class的与成员变量定义 +#### 1.1.1 Template Class的与成员变量定义 我们来回顾一下最基本的Template Class声明和定义形式: Template Class声明: @@ -127,7 +127,7 @@ typedef class { 可以看出,通过模板参数替换类型,可以获得很多形式相同的新类型,有效减少了代码量。这种用法,我们称之为“泛型”(Generic Programming),它最常见的应用,即是STL中的容器模板类。 -####1.1.2 模板的使用 +#### 1.1.2 模板的使用 对于C++来说,类型最重要的作用之一就是用它去产生一个变量。例如我们定义了一个动态数组(列表)的模板类`vector`,它对于任意的元素类型都具有push_back和clear的操作,我们便可以如下定义这个类: @@ -178,7 +178,7 @@ ClassA template class ClassB { - // Class body ... + // Class body ... }; ClassB @@ -187,7 +187,7 @@ ClassB 当然,在实例化过程中,被绑定到模板参数上的类型(即模板实参)需要与模板形参正确匹配。 就如同函数一样,如果没有提供足够并匹配的参数,模板便不能正确的实例化。 -####1.1.3 模板类的成员函数定义 +#### 1.1.3 模板类的成员函数定义 由于C++11正式废弃“模板导出”这一特性,因此在模板类的变量在调用成员函数的时候,需要看到完整的成员函数定义。因此现在的模板类中的成员函数,通常都是以内联的方式实现。 例如: @@ -197,13 +197,13 @@ template class vector { public: - void clear() - { - // Function body - } + void clear() + { + // Function body + } private: - T* elements; + T* elements; }; ``` @@ -214,9 +214,9 @@ template class vector { public: - void clear(); // 注意这里只有声明 + void clear(); // 注意这里只有声明 private: - T* elements; + T* elements; }; template @@ -231,7 +231,7 @@ void vector::clear() // 函数的实现放在这里 ``` C++ void vector::clear() { - // Function body + // Function body } ``` @@ -243,16 +243,16 @@ void vector::clear() 综上,正确的成员函数实现如下所示: ``` C++ -template // 模板参数 -void vector /*看起来像偏特化*/ ::clear() // 函数的实现放在这里 +template // 模板参数 +void vector /*看起来像偏特化*/ ::clear() // 函数的实现放在这里 { - // Function body + // Function body } ``` -###1.2 Template Function的基本语法 +### 1.2 Template Function的基本语法 -####1.2.1 Template Function的声明和定义 +#### 1.2.1 Template Function的声明和定义 模板函数的语法与模板类基本相同,也是以关键字`template`和模板参数列表作为声明与定义的开始。模板参数列表中的类型,可以出现在参数、返回值以及函数体中。比方说下面几个例子 @@ -265,8 +265,8 @@ template U foo(T const&); template void foo() { - T var; - // ... + T var; + // ... } ``` @@ -316,14 +316,14 @@ template void foo() 当然和“设计模式”一样,模板在实际应用中,也会有一些固定的需求和解决方案。比较常见的场景包括:泛型(最基本的用法)、通过类型获得相应的信息(型别萃取)、编译期间的计算、类型间的推导和变换(从一个类型变换成另外一个类型,比如boost::function)。这些本文在以后的章节中会陆续介绍。 -####1.2.2 模板函数的使用 +#### 1.2.2 模板函数的使用 我们先来看一个简单的函数模板,两个数相加: ``` C++ template T Add(T a, T b) { - return a + b; + return a + b; } ``` @@ -399,7 +399,7 @@ float data[1024]; template T GetValue(int i) { - return static_cast(data[i]); + return static_cast(data[i]); } float a = GetValue(0); // 出错了! @@ -428,7 +428,7 @@ DstT dest = c_style_cast(src); ``` C++ template DstT c_style_cast(SrcT v) { - return (DstT)(v); + return (DstT)(v); } int v = 0; @@ -456,14 +456,14 @@ float i = c_style_cast(v); ``` C++ template DstT c_style_cast(SrcT v) // 模版参数 DstT 需要人肉指定,放前面。 { - return (DstT)(v); + return (DstT)(v); } int v = 0; float i = c_style_cast(v); // 形象地说,DstT会先把你指定的参数吃掉,剩下的就交给编译器从函数参数列表中推导啦。 ``` -###1.3 整型也可是Template参数 +### 1.3 整型也可是Template参数 模板参数除了类型外(包括基本类型、结构、类类型等),也可以是一个整型数(Integral Number)。这里的整型数比较宽泛,包括布尔、不同位数、有无符号的整型,甚至包括指针。我们将整型的模板参数和类型作为模板参数来做一个对比: @@ -479,7 +479,7 @@ template class TemplateWithValue; ``` C++ template struct Array { - T data[Size]; + T data[Size]; }; Array arr; @@ -490,7 +490,7 @@ Array arr; ``` C++ class IntArrayWithSize16 { - int data[16]; // int 替换了 T, 16 替换了 Size + int data[16]; // int 替换了 T, 16 替换了 Size }; IntArrayWithSize16 arr; @@ -503,9 +503,9 @@ template class A {}; void foo() { - int x = 3; - A<5> a; // 正确! - A b; // error C2971: '_1_3::A' : template parameter 'i' : 'x' : a local variable cannot be used as a non-type argument + int x = 3; + A<5> a; // 正确! + A b; // error C2971: '_1_3::A' : template parameter 'i' : 'x' : a local variable cannot be used as a non-type argument } ``` 因为x不是一个编译期常量,所以 `A` 就会告诉你,x是一个局部变量,不能作为一个模板参数出现。 @@ -516,9 +516,9 @@ void foo() template class A { public: - void foo(int) - { - } + void foo(int) + { + } }; template class B {}; template class C {}; @@ -526,33 +526,31 @@ template ::*a)(int)> class D {}; template int Add(int a) // 当然也能用于函数模板 { - return a + i; + return a + i; } void foo() { - A<5> a; - B< - 7, A<5>, nullptr - > b; // 模板参数可以是一个无符号八位整数,可以是模板生成的类;可以是一个指针。 - C c; // 模板参数可以是一个bool类型的常量,甚至可以是一个函数指针。 - D<&A<3>::foo> d; // 丧心病狂啊!它还能是一个成员函数指针! - int x = Add<3>(5); // x == 8。因为整型模板参数无法从函数参数获得,所以只能是手工指定啦。 + A<5> a; + B<7, A<5>, nullptr> b; // 模板参数可以是一个无符号八位整数,可以是模板生成的类;可以是一个指针。 + C c; // 模板参数可以是一个bool类型的常量,甚至可以是一个函数指针。 + D<&A<3>::foo> d; // 丧心病狂啊!它还能是一个成员函数指针! + int x = Add<3>(5); // x == 8。因为整型模板参数无法从函数参数获得,所以只能是手工指定啦。 } -template class E {}; // ERROR: 别闹!早说过只能是整数类型的啦! +template class E {}; // ERROR: 别闹!早说过只能是整数类型的啦! ``` 当然,除了单纯的用作常数之外,整型参数还有一些其它的用途。这些“其它”用途最重要的一点是让类型也可以像整数一样运算。《Modern C++ Design》给我们展示了很多这方面的例子。不过你不用急着去阅读那本天书,我们会在做好足够的知识铺垫后,让你轻松学会这些招数。 -###1.4 模板形式与功能是统一的 +### 1.4 模板形式与功能是统一的 第一章走马观花的带着大家复习了一下C++ Template的基本语法形式,也解释了包括 `typename` 在内,类/函数模板写法中各个语法元素的含义。形式是功能的外在体现,介绍它们也是为了让大家能理解到,模板之所以写成这种形式是有必要的,而不是语言的垃圾成分。 从下一章开始,我们便进入了更加复杂和丰富的世界:讨论模板的匹配规则。其中有令人望而生畏的特化与偏特化。但是,请相信我们在序言中所提到的:将模板作为一门语言来看待,它会变得有趣而简单。 ## 2. 模板元编程基础 -###2.1 编程,元编程,模板元编程 +### 2.1 编程,元编程,模板元编程 技术的学习是一个登山的过程。第一章是最为平坦的山脚道路。而从这一章开始,则是正式的爬坡。无论是我写作还是你阅读,都需要付出比第一章更多的代价。那么问题就是,付出更多的精力学习模板是否值得? @@ -699,35 +697,35 @@ for(v4a, v4b : vectorsA, vectorsB) 好吧,我承认这个例子还是太牵强了。不过相信我,在你阅读完第二章和第三章之后,你会将这些特性自如地运用到你的程序之中。你的程序将会变成体现模板“可运算”威力的最好例子。 -###2.2 模板世界的If-Then-Else:类模板的特化与偏特化 +### 2.2 模板世界的If-Then-Else:类模板的特化与偏特化 -####2.2.1 根据类型执行代码 +#### 2.2.1 根据类型执行代码 前一节的示例提出了一个要求:需要做出根据类型执行不同代码。要达成这一目的,模板并不是唯一的途径。比如之前我们所说的重载。如果把眼界放宽一些,虚函数也是根据类型执行代码的例子。此外,在C语言时代,也会有一些技法来达到这个目的,比如下面这个例子,我们需要对两个浮点做加法, 或者对两个整数做乘法: ``` C struct Variant { - union - { - int x; - float y; - } data; - uint32 typeId; + union + { + int x; + float y; + } data; + uint32 typeId; }; Variant addFloatOrMulInt(Variant const* a, Variant const* b) { - Variant ret; - assert(a->typeId == b->typeId); - if (a->typeId == TYPE_INT) - { - ret.x = a->x * b->x; - } - else - { - ret.y = a->y + b->y; - } - return ret; + Variant ret; + assert(a->typeId == b->typeId); + if (a->typeId == TYPE_INT) + { + ret.x = a->x * b->x; + } + else + { + ret.y = a->y + b->y; + } + return ret; } ``` @@ -738,14 +736,14 @@ Variant addFloatOrMulInt(Variant const* a, Variant 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) - { - BIN_OP(int, data0, *, data1, out); - } - else - { - BIN_OP(float, data0, +, data1, out); - } + if(type == TYPE_INT) + { + BIN_OP(int, data0, *, data1, out); + } + else + { + BIN_OP(float, data0, +, data1, out); + } } ``` @@ -757,12 +755,12 @@ IAnimal* animal = GetAnimalFromSystem(); IDog* maybeDog = dynamic_cast(animal); if(maybeDog) { - maybeDog->Wangwang(); + maybeDog->Wangwang(); } ICat* maybeCat = dynamic_cast(animal); if(maybeCat) { - maybeCat->Moemoe(); + maybeCat->Moemoe(); } ``` @@ -781,15 +779,13 @@ template T addFloatOrMulInt(T a, T b); 如果你运用了模板来实现,那么当传入两个不同类型的变量,或者不是 `int` 和 `float` 变量,编译器就会提示错误。但是如果使用了我们前述的 `Variant` 来实现,编译器可就管不了那么多了。但是,成也编译期,败也编译期。最严重的“缺点”,就是你没办法根据用户输入或者别的什么在运行期间可能发生变化的量来决定它产生、或执行什么代码。比如下面的代码段,它是不成立的。 ``` C++ - template int foo() { return i + j; } int main() { - cin >> x >> y; - return foo(); + cin >> x >> y; + return foo(); } - ``` 这点限制也粉碎了妄图用模板来包办工厂(Factory)甚至是反射的梦想。尽管在《Modern C++ Design》中(别问我为什么老举这本书,因为《C++ Templates》和《Generic Programming》我只是囫囵吞枣读过,基本不记得了)大量运用模板来简化工厂方法;同时C++11和14中的一些机制如Variadic Template更是让这一问题的解决更加彻底。但无论如何,直到C++11/14,光靠模板你就是写不出依靠类名或者ID变量产生类型实例的代码。 @@ -808,7 +804,7 @@ Variant result = addFloatOrMulInt(aVar, bVar); 在模板代码中,这个“合适的机制”就是指“特化”和“部分特化(Partial Specialization)”,后者也叫“偏特化”。 -####2.2.2 特化 +#### 2.2.2 特化 我的高中物理老师对我说过一句令我受用至今的话:把自己能做的事情做好。编写模板程序也是一样。当你试图用模板解决问题之前,先撇开那些复杂的语法要素,用最直观的方式表达你的需求: @@ -817,23 +813,23 @@ Variant result = addFloatOrMulInt(aVar, bVar); int|float addFloatOrMulInt(a, b) { - if(type is Int) - { - return a * b; - } - else if (type is Float) - { - return a + b; - } + if(type is Int) + { + return a * b; + } + else if (type is Float) + { + return a + b; + } } void foo() { - float a, b, c; - c = addFloatOrMulInt(a, b); // c = a + b; + float a, b, c; + c = addFloatOrMulInt(a, b); // c = a + b; - int x, y, z; - z = addFloatOrMulInt(x, y); // z = x * y; + int x, y, z; + z = addFloatOrMulInt(x, y); // z = x * y; } ``` @@ -843,26 +839,26 @@ void foo() // 这里仍然是伪代码,意思一下,too。 class AddFloatOrMulInt { - static int|float Do(a, b) + static int|float Do(a, b) + { + if(type is Int) + { + return a * b; + } + else if (type is Float) { - if(type is Int) - { - return a * b; - } - else if (type is Float) - { - return a + b; - } - } + return a + b; + } + } }; void foo() { - float a, b, c; - c = AddFloatOrMulInt::Do(a, b); // c = a + b; + float a, b, c; + c = AddFloatOrMulInt::Do(a, b); // c = a + b; - int x, y, z; - z = AddFloatOrMulInt::Do(x, y); // z = x * y; + int x, y, z; + z = AddFloatOrMulInt::Do(x, y); // z = x * y; } ``` @@ -871,11 +867,11 @@ void foo() ``` C++ void foo() { - float a, b, c; - c = AddFloatOrMulInt::Do(a, b); // c = a + b; + float a, b, c; + c = AddFloatOrMulInt::Do(a, b); // c = a + b; - int x, y, z; - z = AddFloatOrMulInt::Do(x, y); // z = x * y; + int x, y, z; + z = AddFloatOrMulInt::Do(x, y); // z = x * y; } ``` 也许你不明白为什么要改写成现在这个样子。看不懂不怪你,怪我讲的不好。但是你别急,先看看这样改写以后能不能跟我们的目标接近一点。如果我们把 `AddFloatOrMulInt::Do` 看作一个普通的函数,那么我们可以写两个实现出来: @@ -883,21 +879,21 @@ void foo() ``` C++ float AddFloatOrMulInt::Do(float a, float b) { - return a + b; + return a + b; } int AddFloatOrMulInt::Do(int a, int b) { - return a * b; + return a * b; } void foo() { - float a, b, c; - c = AddFloatOrMulInt::Do(a, b); // c = a + b; + float a, b, c; + c = AddFloatOrMulInt::Do(a, b); // c = a + b; - int x, y, z; - z = AddFloatOrMulInt::Do(x, y); // z = x * y; + int x, y, z; + z = AddFloatOrMulInt::Do(x, y); // z = x * y; } ``` @@ -907,32 +903,32 @@ void foo() // 这个是给float用的。 template class AddFloatOrMulInt { - T Do(T a, T b) - { - return a + b; - } + T Do(T a, T b) + { + return a + b; + } }; // 这个是给int用的。 template class AddFloatOrMulInt { - T Do(T a, T b) - { - return a * b; - } + T Do(T a, T b) + { + return a * b; + } }; void foo() { - float a, b, c; + float a, b, c; - // 嗯,我们需要 c = a + b; - c = AddFloatOrMulInt::Do(a, b); - // ... 觉得哪里不对劲 ... - // ... - // ... - // ... - // 啊!有两个AddFloatOrMulInt,class看起来一模一样,要怎么区分呢! + // 嗯,我们需要 c = a + b; + c = AddFloatOrMulInt::Do(a, b); + // ... 觉得哪里不对劲 ... + // ... + // ... + // ... + // 啊!有两个AddFloatOrMulInt,class看起来一模一样,要怎么区分呢! } ``` 好吧,问题来了!如何要让两个内容不同,但是模板参数形式相同的类进行区分呢?特化!特化(specialization)是根据一个或多个特殊的整数或类型,给出模板实例化时的一个指定内容。我们先来看特化是怎么应用到这个问题上的。 @@ -940,37 +936,37 @@ void foo() // 首先,要写出模板的一般形式(原型) template class AddFloatOrMulInt { - static T Do(T a, T b) - { - // 在这个例子里面一般形式里面是什么内容不重要,因为用不上 - // 这里就随便给个0吧。 - return T(0); - } + static T Do(T a, T b) + { + // 在这个例子里面一般形式里面是什么内容不重要,因为用不上 + // 这里就随便给个0吧。 + return T(0); + } }; // 其次,我们要指定T是int时候的代码,这就是特化: template <> class AddFloatOrMulInt { public: - static int Do(int a, int b) // - { - return a * b; - } + static int Do(int a, int b) // + { + return a * b; + } }; // 再次,我们要指定T是float时候的代码: template <> class AddFloatOrMulInt { public: - static float Do(float a, float b) - { - return a + b; - } + static float Do(float a, float b) + { + return a + b; + } }; void foo() { - // 这里面就不写了 + // 这里面就不写了 } ``` 我们再把特化的形式拿出来一瞧:这货有点怪啊: `template <> class AddFloatOrMulInt`。别急,我给你解释一下。 @@ -992,7 +988,7 @@ template class AddFloatOrMulInt; // 所以这里放空。 template <> class AddFloatOrMulInt { - // ... 针对Int的实现 ... + // ... 针对Int的实现 ... } // Bingo! @@ -1005,13 +1001,13 @@ template <> class AddFloatOrMulInt template class TypeToID { public: - static int const ID = -1; + static int const ID = -1; }; template <> class TypeToID { public: - static int const ID = 0; + static int const ID = 0; }; ``` @@ -1020,14 +1016,14 @@ public: ``` C++ void PrintID() { - cout << "ID of uint8_t: " << TypeToID::ID << endl; + cout << "ID of uint8_t: " << TypeToID::ID << endl; } ``` 嗯,看起来挺简单的,是吧。但是这里透露出了一个非常重要的信号,我希望你已经能察觉出来了: `TypeToID` 如同是一个函数。这个函数只能在编译期间执行。它输入一个类型,输出一个ID。 如果你体味到了这一点,那么恭喜你,你的模板元编程已经开悟了。 -####2.2.3 特化:一些其它问题 +#### 2.2.3 特化:一些其它问题 在上一节结束之后,你一定做了许多的练习。我们再来做三个练习。第一,给`float`一个ID;第二,给`void*`一个ID;第三,给任意类型的指针一个ID。先来做第一个: @@ -1038,7 +1034,7 @@ void PrintID() template <> class TypeToID { - static int const ID = 0xF10A7; + static int const ID = 0xF10A7; }; ``` @@ -1047,12 +1043,12 @@ template <> class TypeToID ``` C++ template <> class TypeToID { - static int const ID = 0x401d; + static int const ID = 0x401d; }; void PrintID() { - cout << "ID of uint8_t: " << TypeToID::ID << endl; + cout << "ID of uint8_t: " << TypeToID::ID << endl; } ``` @@ -1061,11 +1057,10 @@ void PrintID() ``` C++ class ClassB {}; -template <> class TypeToID; // 函数的TypeID -template <> class TypeToID; // 数组的TypeID -template <> class TypeToID; // 这是以数组为参数的函数的TypeID -template <> class TypeToID< - int (ClassB::*[3])(void*, float[2])>; // 我也不知道这是什么了,自己看着办吧。 +template <> class TypeToID; // 函数的TypeID +template <> class TypeToID; // 数组的TypeID +template <> class TypeToID; // 这是以数组为参数的函数的TypeID +template <> class TypeToID; // 我也不知道这是什么了,自己看着办吧。 ``` 甚至连 `const` 和 `volatile` 都能装进去 @@ -1079,7 +1074,7 @@ template <> class TypeToID; ``` C++ void PrintID() { - cout << "ID of double: " << TypeToID::ID << endl; + cout << "ID of double: " << TypeToID::ID << endl; } ``` @@ -1091,20 +1086,20 @@ void PrintID() template class TypeToID { public: - static int const NotID = -2; + static int const NotID = -2; }; template <> class TypeToID { public: - static int const ID = 1; + static int const ID = 1; }; 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 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这个成员。 } ``` @@ -1117,14 +1112,14 @@ void PrintID() ``` C void copy(void* dst, void const* src, size_t elemSize, size_t elemCount, void (*copyElem)(void* dstElem, void const* srcElem)) { - void const* reader = src; - void const* writer = dst; - for(size_t i = 0; i < elemCount; ++i) - { - copyElem(writer, reader); - advancePointer(reader, elemSize); // 把Reader指针往后移动一些字节 - advancePointer(writer, elemSize); - } + void const* reader = src; + void const* writer = dst; + for(size_t i = 0; i < elemCount; ++i) + { + copyElem(writer, reader); + advancePointer(reader, elemSize); // 把Reader指针往后移动一些字节 + advancePointer(writer, elemSize); + } } ``` @@ -1161,10 +1156,10 @@ void copy(T* dst, T const* src, size_t elemCount); template void copy(T* dst, T const* src, size_t elemCount) { - for(size_t i = 0; i < elemCount; ++i) - { - dst[i] = src[i]; - } + for(size_t i = 0; i < elemCount; ++i) + { + dst[i] = src[i]; + } } ``` @@ -1173,11 +1168,11 @@ void copy(T* dst, T const* src, size_t elemCount) 最后,我们把函数模板学到的东西,也应用到类模板里面: ``` C++ -template // 嗯,需要一个T -class TypeToID // 我要对所有的指针类型特化,所以这里就写T* +template // 嗯,需要一个T +class TypeToID // 我要对所有的指针类型特化,所以这里就写T* { public: - static int const ID = 0x80000000; // 用最高位表示它是一个指针 + static int const ID = 0x80000000; // 用最高位表示它是一个指针 }; ``` @@ -1186,7 +1181,7 @@ public: ``` C++ void PrintID() { - cout << "ID of float*: " << TypeToID::ID << endl; + cout << "ID of float*: " << TypeToID::ID << endl; } ``` @@ -1197,12 +1192,12 @@ void PrintID() // TypeToID 的其他代码,略过不表 // ... -template // 嗯,需要一个T -class TypeToID // 我要对所有的指针类型特化,所以这里就写T* +template // 嗯,需要一个T +class TypeToID // 我要对所有的指针类型特化,所以这里就写T* { public: - typedef T SameAsT; - static int const ID = 0x80000000; // 用最高位表示它是一个指针 + typedef T SameAsT; + static int const ID = 0x80000000; // 用最高位表示它是一个指针 }; void PrintID() @@ -1223,20 +1218,20 @@ OK,猜出来了吗,T是`float`。为什么呢?因为你用 `float *` 匹 template class RemovePointer { - // 啥都不干,你要放一个不是指针的类型进来,我就让你死的难看。 + // 啥都不干,你要放一个不是指针的类型进来,我就让你死的难看。 }; template class RemovePointer // 祖传牛皮藓,专治各类指针 { public: - typedef T Result; + typedef T Result; }; void Foo() { - RemovePointer::Result x = 5.0f; // 喏,用RemovePointer后,那个Result就是把float*的指针处理掉以后的结果:float啦。 - std::cout << x << std::endl; + RemovePointer::Result x = 5.0f; // 喏,用RemovePointer后,那个Result就是把float*的指针处理掉以后的结果:float啦。 + std::cout << x << std::endl; } ``` @@ -1247,24 +1242,24 @@ OK,如果这个时候,我需要给 `int*` 提供一个更加特殊的特化 // TypeToID 的其他代码,略过不表 // ... -template // 嗯,需要一个T -class TypeToID // 我要对所有的指针类型特化,所以这里就写T* +template // 嗯,需要一个T +class TypeToID // 我要对所有的指针类型特化,所以这里就写T* { public: - typedef T SameAsT; - static int const ID = 0x80000000; // 用最高位表示它是一个指针 + typedef T SameAsT; + static int const ID = 0x80000000; // 用最高位表示它是一个指针 }; -template <> // 嗯,int* 已经是个具体的不能再具体的类型了,所以模板不需要额外的类型参数了 -class TypeToID // 嗯,对int*的特化。在这里呢,要把int*整体看作一个类型。 +template <> // 嗯,int* 已经是个具体的不能再具体的类型了,所以模板不需要额外的类型参数了 +class TypeToID // 嗯,对int*的特化。在这里呢,要把int*整体看作一个类型。 { public: - static int const ID = 0x12345678; // 给一个缺心眼的ID + static int const ID = 0x12345678; // 给一个缺心眼的ID }; void PrintID() { - cout << "ID of int*: " << TypeToID::ID << endl; + cout << "ID of int*: " << TypeToID::ID << endl; } ``` @@ -1288,17 +1283,17 @@ template struct X {}; template struct Y { - typedef X ReboundType; // 类型定义1 - typedef typename X::MemberType MemberType; // 类型定义2 - typedef UnknownType MemberType3; // 类型定义3 + typedef X ReboundType; // 类型定义1 + typedef typename X::MemberType MemberType; // 类型定义2 + typedef UnknownType MemberType3; // 类型定义3 - void foo() - { - X instance0; - typename X::MemberType instance1; - WTF instance2 - 大王叫我来巡山 - + & - } + void foo() + { + X instance0; + typename X::MemberType instance1; + WTF instance2 + 大王叫我来巡山 - + & + } }; ``` @@ -1312,7 +1307,7 @@ template struct Y 这时我们就需要请出C++11标准 —— 中的某些概念了。这是我们到目前为止第一次参阅标准。我希望能尽量减少直接参阅标准的次数,因此即便是极为复杂的模板匹配决议我都暂时没有引入标准中的描述。 然而,Template引入的“双阶段名称查找(Two phase name lookup)”堪称是C++中最黑暗的角落 —— 这是LLVM的团队自己在博客上说的 —— 因此在这里,我们还是有必要去了解标准中是如何规定的。 -####2.3.2 名称查找:I am who I am +#### 2.3.2 名称查找:I am who I am 在C++标准中对于“名称查找(name lookup)”这个高大上的名词的诠释,主要集中出现在三处。第一处是3.4节,标题名就叫“Name Lookup”;第二处在10.2节,继承关系中的名称查找;第三处在14.6节,名称解析(name resolution)。 名称查找/名称解析,是编译器的基石。对编译原理稍有了解的人,都知道“符号表”的存在即重要意义。考虑一段最基本的C代码: @@ -1465,13 +1460,13 @@ template struct X {}; template struct Y { - typedef X ReboundType; // 类型定义1 - void foo() - { - X instance0; - X::MemberType instance1; - WTF instance2 - } + typedef X ReboundType; // 类型定义1 + void foo() + { + X instance0; + X::MemberType instance1; + WTF instance2 + } }; void poo(){ @@ -1574,7 +1569,7 @@ void main() { 扩展阅读: [The Dreaded Two-Phase Name Lookup][2] -####2.3.3 “多余的” typename 关键字 +#### 2.3.3 “多余的” typename 关键字 到了这里,2.3.1 中提到的四个问题,还有三个没有解决: @@ -1583,9 +1578,9 @@ template struct X {}; template struct Y { - typedef X ReboundType; // 这里为什么是正确的? - typedef typename X::MemberType MemberType2; // 这里的typename是做什么的? - typedef UnknownType MemberType3; // 这里为什么会出错? + typedef X ReboundType; // 这里为什么是正确的? + typedef typename X::MemberType MemberType2; // 这里的typename是做什么的? + typedef UnknownType MemberType3; // 这里为什么会出错? }; ``` @@ -1596,16 +1591,16 @@ template struct Y { // X可以查找到原型; // X是一个依赖性名称,模板定义阶段并不管X是不是正确的。 - typedef X ReboundType; + typedef X ReboundType; - // X可以查找到原型; - // X是一个依赖性名称,X::MemberType也是一个依赖性名称; - // 所以模板声明时也不会管X模板里面有没有MemberType这回事。 - typedef typename X::MemberType MemberType2; + // X可以查找到原型; + // X是一个依赖性名称,X::MemberType也是一个依赖性名称; + // 所以模板声明时也不会管X模板里面有没有MemberType这回事。 + typedef typename X::MemberType MemberType2; - // UnknownType 不是一个依赖性名称 - // 而且这个名字在当前作用域中不存在,所以直接报错。 - typedef UnknownType MemberType3; + // UnknownType 不是一个依赖性名称 + // 而且这个名字在当前作用域中不存在,所以直接报错。 + typedef UnknownType MemberType3; }; ``` @@ -1662,9 +1657,9 @@ template struct X { ## 3 深入理解特化与偏特化 -###3.1 正确的理解偏特化 +### 3.1 正确的理解偏特化 -####3.1.1 偏特化与函数重载的比较 +#### 3.1.1 偏特化与函数重载的比较 在前面的章节中,我们介绍了偏特化的形式、也介绍了简单的用例。因为偏特化和函数重载存在着形式上的相似性,因此初学者便会借用重载的概念,来理解偏特化的行为。只是,重载和偏特化尽管相似但仍有差异。 @@ -1937,7 +1932,7 @@ template class Y {}; // (4) error! 在这里,我们只提到了变长模板参数的声明,如何使用我们将在第四章讲述。 -####3.1.3 模板的默认实参 +#### 3.1.3 模板的默认实参 在上一节中,我们介绍了模板对默认实参的支持。当时我们的例子很简单,默认模板实参是一个确定的类型`void`或者自定义的`null_type`: @@ -2084,7 +2079,7 @@ void foo(){ * A和B都与模板实参无法匹配,所以使用原型,调用`CustomDiv` -###3.2 后悔药:SFINAE +### 3.2 后悔药:SFINAE 考虑下面这个函数模板: @@ -2538,34 +2533,34 @@ void foo( (补充例子:构造函数上的enable_if) ## 4 元编程下的数据结构与算法 -###4.1 表达式与数值计算 -###4.1 获得类型的属性——类型萃取(Type Traits) -###4.2 列表与数组 -###4.3 字典结构 -###4.4 “快速”排序 -###4.5 其它常用的“轮子” +### 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.1 嵌入类 +### 5.2 Template-Template Class +### 5.3 高阶函数 +### 5.4 闭包:模板的“基于对象” stl allocator? mpl::apply -###5.5 占位符(placeholder):在C++中实现方言的基石 -###5.6 编译期“多态” +### 5.5 占位符(placeholder):在C++中实现方言的基石 +### 5.6 编译期“多态” ## 6 模板的威力:从foreach, transform到Linq -###6.1 Foreach与Transform -###6.2 Boost中的模板 +### 6.1 Foreach与Transform +### 6.2 Boost中的模板 Any Spirit Hana TypeErasure -###6.3 Reactor、Linq与C++中的实践 -###6.4 更高更快更强:从Linq到FP +### 6.3 Reactor、Linq与C++中的实践 +### 6.4 更高更快更强:从Linq到FP ## 7 结语:讨论有益,争端无用 -###7.1 更好的编译器,更友善的出错信息 -###7.2 模板的症结:易于实现,难于完美 -###7.3 一些期望 +### 7.1 更好的编译器,更友善的出错信息 +### 7.2 模板的症结:易于实现,难于完美 +### 7.3 一些期望 alexandrescu 关于 min max 的讨论:《再谈Min和Max》 std::experimental::any / boost.any 对于 reference 的处理 From 1cef4fcca2f88ad1991a750f4bd6b799e4259eed Mon Sep 17 00:00:00 2001 From: YIN LIU Date: Tue, 21 Mar 2017 22:17:06 +0800 Subject: [PATCH 70/99] Fixed the format. --- ReadMe.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index f239101..40e1d90 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1269,9 +1269,9 @@ void PrintID() 但是呢,直觉对付更加复杂的问题还是没用的(也不是没用,主要是你没这个直觉了)。我们要把这个直觉,转换成合理的规则——即模板的匹配规则。 当然,这个匹配规则是对复杂问题用的,所以我们会到实在一眼看不出来的时候才会动用它。一开始我们只要把握:模板是从最特殊到最一般形式进行匹配就可以了。 -###2.3 即用即推导 +### 2.3 即用即推导 -####2.3.1 视若无睹的语法错误 +#### 2.3.1 视若无睹的语法错误 这一节我们将讲述模板一个非常重要的行为特点:那就是什么时候编译器会对模板进行推导,推导到什么程度。 这一知识,对于理解模板的编译期行为、以及修正模板编译错误都非常重要。 From 35af19af965da47b539fccb27f7b9acbb8017b52 Mon Sep 17 00:00:00 2001 From: FanYang Date: Thu, 8 Jun 2017 12:29:50 +0800 Subject: [PATCH 71/99] Create ReadMe.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改一处笔误 --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 40e1d90..f63a05d 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1483,7 +1483,7 @@ error C2039: 'MemberType': is not a member of 'X' T=float ] ``` -然后是一些语法错误,比如`MemberType`不是一个合法的标识符之类的。这样甚至你会误以为`int`情况下模板的实力化是正确的。虽然在有了经验之后会发现这个问题挺荒唐的,但是仍然会让新手有困惑。 +然后是一些语法错误,比如`MemberType`不是一个合法的标识符之类的。这样甚至你会误以为`int`情况下模板的实例化是正确的。虽然在有了经验之后会发现这个问题挺荒唐的,但是仍然会让新手有困惑。 相比之下,更加遵守标准的Clang在错误提示上就要清晰许多: From fd1c8f1c6909f51a0af38176f88bcaf238494210 Mon Sep 17 00:00:00 2001 From: vczf Date: Mon, 12 Jun 2017 17:22:22 +0800 Subject: [PATCH 72/99] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=96=87=E5=AD=97?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 40e1d90..884a710 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -449,12 +449,12 @@ float i = c_style_cast(v); 嗯,很顺利的通过了。难道C++不能支持让参数推导一部分模板参数吗? -当然是可以的。只不过在部分推导、部分指定的情况下,编译器对模版参数的顺序是有限制的:先写需要指定的模板参数,再把能推导出来的模板参数放在后面。 +当然是可以的。只不过在部分推导、部分指定的情况下,编译器对模板参数的顺序是有限制的:先写需要指定的模板参数,再把能推导出来的模板参数放在后面。 在这个例子中,能推导出来的是 `SrcT`,需要指定的是 `DstT`。把函数模板写成下面这样就可以了: ``` C++ -template DstT c_style_cast(SrcT v) // 模版参数 DstT 需要人肉指定,放前面。 +template DstT c_style_cast(SrcT v) // 模板参数 DstT 需要人肉指定,放前面。 { return (DstT)(v); } @@ -1367,7 +1367,7 @@ template foo(T& v0, C& v1){ 1. 函数`foo`中的变量`v1`已经确定是`struct C`的实例,所以,`v1.a = 2;`会导致编译错误,`v1.c = 3;`是正确的代码; 2. 对于变量`v0`来说,这个问题就变得很微妙。如果`v0`是`struct A`或者`struct AB`的实例,那么`foo`中的语句`v0.a = 1;`就是正确的。如果是`struct C`,那么这段代码就是错误的。 -因此在模板定义的地方进行语义分析,并不能**完全**得出代码是正确或者错误的结论,只有到了实例化阶段,确定了模版参数的类型后,才知道这段代码正确与否。令人高兴的是,在这一问题上,我们和C++标准委员会的见地一致,说明我们的C++水平已经和Herb Sutter不分伯仲了。既然我们和Herb Sutter水平差不多,那凭什么人家就吃香喝辣?下面我们来选几条标准看看服不服: +因此在模板定义的地方进行语义分析,并不能**完全**得出代码是正确或者错误的结论,只有到了实例化阶段,确定了模板参数的类型后,才知道这段代码正确与否。令人高兴的是,在这一问题上,我们和C++标准委员会的见地一致,说明我们的C++水平已经和Herb Sutter不分伯仲了。既然我们和Herb Sutter水平差不多,那凭什么人家就吃香喝辣?下面我们来选几条标准看看服不服: > **14.6 名称解析(Name resolution)** From dbc77e27d5e0dc0c6df3ddb7d401b3884dc45124 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Wed, 14 Jun 2017 13:50:29 -0700 Subject: [PATCH 73/99] Add memo for missed content. --- ReadMe.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index 40e1d90..9158c2c 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -82,6 +82,9 @@ void SampleCode() { * 模板的使用动机。 * 增加“如何使用本文”一节。本节将说明全书的体例(强调字体、提示语、例子的组织),所有的描述、举例、引用在重审时将按照体例要求重新组织。 * 除了用于描述语法的例子外,其他例子将尽量赋予实际意义,以方便阐述意图。 + * 在合适的章节完整叙述模板的类型推导规则。Parameter-Argument, auto variable, decltype, decltype(auto) + * 在函数模板重载和实例化的部分讲述ADL。 + * 变参模板处应当按照标准(Argument Packing/Unpacking)来讲解。 * 建议: * 比较模板和函数的差异性 * 蓝色:C++14 Return type deduction for normal functions 的分析 From c146b6e21201878e2b66f18820df9f0d42dfef53 Mon Sep 17 00:00:00 2001 From: LH_Mouse Date: Fri, 9 Jun 2017 00:52:04 +0800 Subject: [PATCH 74/99] Don't use reserved identifiers in examples. --- ReadMe.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 10af4f0..5450b75 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1621,26 +1621,26 @@ template struct Y struct A; template struct B; template struct X { - typedef X _A; // 编译器当然知道 X 是一个类型。 - typedef X _B; // X 等价于 X 的缩写 - typedef T _C; // T 不是一个类型还玩毛 + typedef X TA; // 编译器当然知道 X 是一个类型。 + typedef X TB; // X 等价于 X 的缩写 + typedef T TC; // T 不是一个类型还玩毛 // !!!注意我要变形了!!! class Y { - typedef X _D; // X 的内部,既然外部高枕无忧,内部更不用说了 - typedef X::Y _E; // 嗯,这里也没问题,编译器知道Y就是当前的类型, + typedef X TD; // X 的内部,既然外部高枕无忧,内部更不用说了 + typedef X::Y TE; // 嗯,这里也没问题,编译器知道Y就是当前的类型, // 这里在VS2015上会有错,需要添加 typename, // Clang 上顺利通过。 - typedef typename X::Y _F; // 这个居然要加 typename! + typedef typename X::Y TF; // 这个居然要加 typename! // 因为,X和X不一样哦, // 它可能会在实例化的时候被别的偏特化给抢过去实现了。 }; - typedef A _G; // 嗯,没问题,A在外面声明啦 - typedef B _H; // B也是一个类型 - typedef typename B::type _I; // 嗯,因为不知道B::type的信息, + typedef A TG; // 嗯,没问题,A在外面声明啦 + typedef B TH; // B也是一个类型 + typedef typename B::type TI; // 嗯,因为不知道B::type的信息, // 所以需要typename - typedef B::type _J; // B 不依赖模板参数, + typedef B::type TJ; // B 不依赖模板参数, // 所以编译器直接就实例化(instantiate)了 // 但是这个时候,B并没有被实现,所以就出错了 }; @@ -1654,7 +1654,7 @@ template struct X { 2. 在 2.3.3 一节我们插入了C++模板中最难理解的内容之一:名称查找。名称查找是语义分析的一个环节,模板内书写的 **变量声明**、**typedef**、**类型名称** 甚至 **类模板中成员函数的实现** 都要符合名称查找的规矩才不会出错; -3. C++编译器对语义的分析的原则是“大胆假设,小心求证”:在能求证的地方尽量求证 —— 比如两段式名称查找的第一阶段;无法检查的地方假设你是正确的 —— 比如`typedef typename A::MemberType _X;`在模板定义时因为`T`不明确不会轻易判定这个语句的死刑。 +3. C++编译器对语义的分析的原则是“大胆假设,小心求证”:在能求证的地方尽量求证 —— 比如两段式名称查找的第一阶段;无法检查的地方假设你是正确的 —— 比如`typedef typename A::MemberType X;`在模板定义时因为`T`不明确不会轻易判定这个语句的死刑。 从下一章开始,我们将进入元编程环节。我们将使用大量的示例,一方面帮助巩固大家学到的模板知识,一方面也会引导大家使用函数式思维去解决常见的问题。 From 83bc7817c5fd00d361ed11768c54e309738b8724 Mon Sep 17 00:00:00 2001 From: LH_Mouse Date: Fri, 9 Jun 2017 01:10:19 +0800 Subject: [PATCH 75/99] Elaborate the cause of emergence of `typename` before dependent names that designate types. --- ReadMe.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 10af4f0..09f9c09 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1613,7 +1613,24 @@ template struct Y 事实上,标准对`typename`的使用规定极为复杂,也算是整个模板中的难点之一。如果想了解所有的标准,需要阅读标准14.6节下2-7条,以及14.6.2.1第一条中对于`current instantiation`的解释。 -简单来说,如果编译器能在出现的时候知道它的类型,那么就不需要`typename`,如果必须要到实例化的时候才能知道它是不是合法,那么定义的时候就把这个名称作为变量而不是类型。 +简单来说,如果编译器能在出现的时候知道它是一个类型,那么就不需要`typename`,如果必须要到实例化的时候才能知道它是不是合法,那么定义的时候就把这个名称作为变量而不是类型。 + +我们用一行代码来说明这个问题: + +```C++ +a * b; +``` + +在没有模板的情况下,这个语句有两种可能的意思:如果`a`是一个类型,这就是定义了一个指针`b`,它拥有类型`a*`;如果`a`是一个对象或引用,这就是计算一个表达式`a*b`,虽然结果并没有保存下来。可是如果上面的`a`是模板参数的成员,会发生什么呢? + +```C++ +template void meow() +{ +    T::a * b; // 这是指针定义还是表达式语句? +} +``` + +编译器对模板进行语法检查的时候,必须要知道上面那一行到底是个什么——这当然可以推迟到实例化的时候进行(比如VC,这也是上面说过VC可以不加`typename`的原因),不过那是另一个故事了——显然在模板定义的时候,编译器并不能妄断。因此,C++标准规定,在没有`typename`约束的情况下认为这里`T::a`不是类型,因此`T::a * b;` 会被当作表达式语句(例如乘法);而为了告诉编译器这是一个指针的定义,我们必须在`T::a`之前加上`typename`关键字,告诉编译器`T::a`是一个类型,这样整个语句才能符合指针定义的语法。 在这里,我举几个例子帮助大家理解`typename`的用法,这几个例子已经足以涵盖日常使用[(预览)][3]: From 7c182f5c170fb6b5d6f5a8592e2424284d9ff433 Mon Sep 17 00:00:00 2001 From: Alinshans <13925588730@163.com> Date: Fri, 4 Aug 2017 01:34:15 +0800 Subject: [PATCH 76/99] :pencil: Made some changes. --- ReadMe.md | 117 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 67 insertions(+), 50 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 10af4f0..79d9f11 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -5,7 +5,7 @@ ### 0.1 C++另类简介:比你用的复杂,但比你想的简单 -C++似乎从他为世人所知的那天开始便成为天然的话题性编程语言。在它在周围有着形形色色的赞美与贬低之词。当我在微博上透露欲写此文的意愿时,也收到了很多褒贬不一的评论。作为一门语言,能拥有这么多使用并恨着它、使用并畏惧它的用户,也算是语言丛林里的奇观了。 +C++似乎从它为世人所知的那天开始便成为天然的话题性编程语言。在它在周围有着形形色色的赞美与贬低之词。当我在微博上透露欲写此文的意愿时,也收到了很多褒贬不一的评论。作为一门语言,能拥有这么多使用并恨着它、使用并畏惧它的用户,也算是语言丛林里的奇观了。 C++之所以变成一门层次丰富、结构多变、语法繁冗的语言,是有着多层次的原因的。Bjarne在《The Design and Evolution of C++》一书中,详细的解释了C++为什么会变成如今(C++98/03)的模样。这本书也是我和陈梓瀚一直对各位已经入门的新手强烈推荐的一本书。通过它你多少可以明白,C++的诸多语法要素之所以变成如今的模样,实属迫不得已。 @@ -19,7 +19,7 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言, 2002年出版的另一本书《C++ Templates》,可以说是在Template方面的集大成之作。它详细阐述了模板的语法、提供了和模板有关的语言细节信息,举了很多有代表性例子。但是对于模板新手来说,这本书细节如此丰富,让他们随随便便就打了退堂鼓缴械投降。 -本文的写作初衷,就是通过“编程语言”的视角,介绍一个简单、清晰的“模板语言”。我会尽可能的将模板的诸多要素连串起来,用一些简单的例子帮助读者学习这门“语言”,让读者在编写、阅读模板代码的时候,能像 `if(exp) { dosomething(); }`一样的信手拈来,让“模板元编程”技术成为读者牢固掌握、可举一反三的有用技能。 +本文的写作初衷,就是通过“编程语言”的视角,介绍一个简单、清晰的“模板语言”。我会尽可能地将模板的诸多要素连串起来,用一些简单的例子帮助读者学习这门“语言”,让读者在编写、阅读模板代码的时候,能像 `if(exp) { dosomething(); }`一样的信手拈来,让“模板元编程”技术成为读者牢固掌握、可举一反三的有用技能。 ### 0.2 适宜读者群 @@ -115,7 +115,7 @@ template class ClassA void foo(int a); ``` -`T`则可以类比为函数形参`a`,这里的“模板形参”`T`,也同函数形参一样取成任何你想要的名字;`typename`则类似于例子中函数参数类型`int`,它表示模板参数中的`T`将匹配一个类型。除了 `typename` 之外,我们再后面还要讲到,整型也可以作为模板的参数。 +`T`则可以类比为函数形参`a`,这里的“模板形参”`T`,也同函数形参一样取成任何你想要的名字;`typename`则类似于例子中函数参数类型`int`,它表示模板参数中的`T`将匹配一个类型。除了 `typename` 之外,我们在后面还要讲到,整型也可以作为模板的参数。 在定义完模板参数之后,便可以定义你所需要的类。不过在定义类的时候,除了一般类可以使用的类型外,你还可以使用在模板参数中使用的类型 `T`。可以说,这个 `T`是模板的精髓,因为你可以通过指定模板实参,将T替换成你所需要的类型。 @@ -162,7 +162,7 @@ floatArray.push_back(3.0f); ``` 变量定义的过程可以分成两步来看:第一步,`vector`将`int`绑定到模板类`vector`上,获得了一个“普通的类`vector`”;第二步通过“vector”定义了一个变量。 -与“普通的类”不同,模板类是不能直接用来定义变量的。例如 +与“普通的类”不同,模板类是不能直接用来定义变量的。例如: ```C++ vector unknownVector; // 错误示例 @@ -217,13 +217,13 @@ template class vector { public: - void clear(); // 注意这里只有声明 + void clear(); // 注意这里只有声明 private: T* elements; }; template -void vector::clear() // 函数的实现放在这里 +void vector::clear() // 函数的实现放在这里 { // Function body } @@ -299,7 +299,7 @@ template void foo() 如何才能克服这一问题,最终视模板如平坦代码呢? -答案只有一个:无他,唯手熟尔。 +答案只有一个:**无他,唯手熟尔**。 在学习模板的时候,要反复做以下的思考和练习: @@ -365,7 +365,7 @@ int b = 3; int result = Add(a, b); ``` -编译器会心领神会的将 `Add` 变成 `Add`。但是编译器不能面对模棱两可的答案。比如你这么写的话呢? +编译器会心领神会地将 `Add` 变成 `Add`。但是编译器不能面对模棱两可的答案。比如你这么写的话呢? ``` C++ int a = 5; @@ -390,7 +390,7 @@ template class A {}; template T foo( A v ); A v; -foo(v); // 它能准确的猜到 T 是 int. +foo(v); // 它能准确地猜到 T 是 int. ``` 咦,编译器居然绕过了A这个外套,猜到了 `T` 匹配的是 `int`。编译器是怎么完成这一“魔法”的,我们暂且不表,2.2节时再和盘托出。 @@ -452,7 +452,7 @@ float i = c_style_cast(v); 嗯,很顺利的通过了。难道C++不能支持让参数推导一部分模板参数吗? -当然是可以的。只不过在部分推导、部分指定的情况下,编译器对模板参数的顺序是有限制的:先写需要指定的模板参数,再把能推导出来的模板参数放在后面。 +当然是可以的。只不过在部分推导、部分指定的情况下,编译器对模板参数的顺序是有限制的:**先写需要指定的模板参数,再把能推导出来的模板参数放在后面**。 在这个例子中,能推导出来的是 `SrcT`,需要指定的是 `DstT`。把函数模板写成下面这样就可以了: @@ -468,7 +468,7 @@ float i = c_style_cast(v); // 形象地说,DstT会先把你指定的 ### 1.3 整型也可是Template参数 -模板参数除了类型外(包括基本类型、结构、类类型等),也可以是一个整型数(Integral Number)。这里的整型数比较宽泛,包括布尔、不同位数、有无符号的整型,甚至包括指针。我们将整型的模板参数和类型作为模板参数来做一个对比: +模板参数除了类型外(包括基本类型、结构、类类型等),也可以是一个整型数(Integral Number)。这里的整型数比较宽泛,包括布尔型,不同位数、有无符号的整型,甚至包括指针。我们将整型的模板参数和类型作为模板参数来做一个对比: ``` C++ template class TemplateWithType; @@ -499,7 +499,7 @@ class IntArrayWithSize16 IntArrayWithSize16 arr; ``` -其中有一点要注意的是,因为模板的匹配是在编译的时候完成的,所以实例化模板的时候所使用的参数,也必须要在编译期就能确定。例如以下的例子编译器就会报错: +其中有一点需要注意,因为模板的匹配是在编译的时候完成的,所以实例化模板的时候所使用的参数,也必须要在编译期就能确定。例如以下的例子编译器就会报错: ``` C++ template class A {}; @@ -536,9 +536,9 @@ void foo() { A<5> a; B<7, A<5>, nullptr> b; // 模板参数可以是一个无符号八位整数,可以是模板生成的类;可以是一个指针。 - C c; // 模板参数可以是一个bool类型的常量,甚至可以是一个函数指针。 - D<&A<3>::foo> d; // 丧心病狂啊!它还能是一个成员函数指针! - int x = Add<3>(5); // x == 8。因为整型模板参数无法从函数参数获得,所以只能是手工指定啦。 + C c; // 模板参数可以是一个bool类型的常量,甚至可以是一个函数指针。 + D<&A<3>::foo> d; // 丧心病狂啊!它还能是一个成员函数指针! + int x = Add<3>(5); // x == 8。因为整型模板参数无法从函数参数获得,所以只能是手工指定啦。 } template class E {}; // ERROR: 别闹!早说过只能是整数类型的啦! @@ -557,9 +557,9 @@ template class E {}; // ERROR: 别闹!早说过只能是整数类型 技术的学习是一个登山的过程。第一章是最为平坦的山脚道路。而从这一章开始,则是正式的爬坡。无论是我写作还是你阅读,都需要付出比第一章更多的代价。那么问题就是,付出更多的精力学习模板是否值得? -这个问题很功利,但是一阵见血。因为技术的根本目的在于解决需求。那C++的模板能做什么? +这个问题很功利,但是一针见血。因为技术的根本目的在于解决需求。那C++的模板能做什么? -一个高(树)大(新)上(蜂)的回答是,C++里面的模板,犹如C中的宏、C#和Java中的自省(restropection)和反射(reflection)一样,是一个改变语言内涵,拓展语言外延的存在。 +一个高(树)大(新)上(风)的回答是,C++里面的模板,犹如C中的宏、C#和Java中的自省(restropection)和反射(reflection),是一个改变语言内涵,拓展语言外延的存在。 程序最根本的目的是什么?复现真实世界或人所构想的规律,减少重复工作的成本,或通过提升规模完成人所不能及之事。但是世间之事万千,有限的程序如何重现复杂的世界呢? @@ -648,13 +648,13 @@ typedef Stack StackInt; typedef Stack StackFloat; ``` -通过模板,我们可以将形形色色的堆栈代码分为两个部分,一个部分是不变的接口,以及近乎相同的实现;另外一部分是元素的类型,它们是需要变化的。因此同函数类似,需要变化的部分,由模板参数来反应;不变的部分,则是模板内的代码。可以看到,使用模板的代码,要比不使用模板的代码简洁许多。 +通过模板,我们可以将形形色色的堆栈代码分为两个部分,一个部分是不变的接口,以及近乎相同的实现;另外一部分是元素的类型,它们是需要变化的。因此同函数类似,需要变化的部分,由模板参数来反映;不变的部分,则是模板内的代码。可以看到,使用模板的代码,要比不使用模板的代码简洁许多。 -如果元编程中所有的变化的量(或者说元编程的参数),都是类型,那么这样的编程,我们有个特定的称呼,叫“泛型”。 +如果元编程中所有变化的量(或者说元编程的参数),都是类型,那么这样的编程,我们有个特定的称呼,叫“泛型”。 但是你会问,模板的发明,仅仅是为了做和宏几乎一样的替换工作吗?可以说是,也可以说不是。一方面,很多时候模板就是为了替换类型,这个时候作用上其实和宏没什么区别。只是宏是基于文本的替换,被替换的文本本身没有任何语义。只有替换完成,编译器才能进行接下来的处理。而模板会在分析模板时以及实例化模板时时候都会进行检查,而且源代码中也能与调试符号一一对应,所以无论是编译时还是运行时,排错都相对简单。 -但是模板也和宏有很大的不同,否则此文也就不能成立了。模板最大的不同在于它是“可以运算”的。我们来举一个例子,不过可能有点牵强。考虑我们要写一个向量逐分量乘法。只不过这个向量,它非常的大。所以为了保证速度,我们需要使用SIMD指令进行加速。假设我们有以下指令可以使用: +但是模板和宏也有很大的不同,否则此文也就不能成立了。模板最大的不同在于它是“可以运算”的。我们来举一个例子,不过可能有点牵强。考虑我们要写一个向量逐分量乘法。只不过这个向量,它非常的大。所以为了保证速度,我们需要使用SIMD指令进行加速。假设我们有以下指令可以使用: ``` Int8,16: N/A @@ -791,7 +791,7 @@ int main() } ``` -这点限制也粉碎了妄图用模板来包办工厂(Factory)甚至是反射的梦想。尽管在《Modern C++ Design》中(别问我为什么老举这本书,因为《C++ Templates》和《Generic Programming》我只是囫囵吞枣读过,基本不记得了)大量运用模板来简化工厂方法;同时C++11和14中的一些机制如Variadic Template更是让这一问题的解决更加彻底。但无论如何,直到C++11/14,光靠模板你就是写不出依靠类名或者ID变量产生类型实例的代码。 +这点限制也粉碎了妄图用模板来包办工厂(Factory)甚至是反射的梦想。尽管在《Modern C++ Design》中(别问我为什么老举这本书,因为《C++ Templates》和《Generic Programming》我只是囫囵吞枣读过,基本不记得了)大量运用模板来简化工厂方法;同时C++11/14中的一些机制如Variadic Template更是让这一问题的解决更加彻底。但无论如何,直到C++11/14,光靠模板你就是写不出依靠类名或者ID变量产生类型实例的代码。 所以说,从能力上来看,模板能做的事情都是编译期完成的。编译期完成的意思就是,当你编译一个程序的时候,所有的量就都已经确定了。比如下面的这个例子: @@ -877,7 +877,7 @@ void foo() z = AddFloatOrMulInt::Do(x, y); // z = x * y; } ``` -也许你不明白为什么要改写成现在这个样子。看不懂不怪你,怪我讲的不好。但是你别急,先看看这样改写以后能不能跟我们的目标接近一点。如果我们把 `AddFloatOrMulInt::Do` 看作一个普通的函数,那么我们可以写两个实现出来: +也许你不明白为什么要改写成现在这个样子。看不懂不怪你,怪我讲得不好。但是你别急,先看看这样改写以后能不能跟我们的目标接近一点。如果我们把 `AddFloatOrMulInt::Do` 看作一个普通的函数,那么我们可以写两个实现出来: ``` C++ float AddFloatOrMulInt::Do(float a, float b) @@ -1041,7 +1041,7 @@ template <> class TypeToID }; ``` -嗯, 这个你已经了然于心了。那么`void*`呢?你想了想,这已经是一个复合类型了。不错你还是战战兢兢的写了下来: +嗯, 这个你已经了然于心了。那么`void*`呢?你想了想,这已经是一个复合类型了。不错你还是战战兢兢地写了下来: ``` C++ template <> class TypeToID @@ -1060,13 +1060,13 @@ void PrintID() ``` C++ class ClassB {}; -template <> class TypeToID; // 函数的TypeID -template <> class TypeToID; // 数组的TypeID +template <> class TypeToID; // 函数的TypeID +template <> class TypeToID; // 数组的TypeID template <> class TypeToID; // 这是以数组为参数的函数的TypeID template <> class TypeToID; // 我也不知道这是什么了,自己看着办吧。 ``` -甚至连 `const` 和 `volatile` 都能装进去 +甚至连 `const` 和 `volatile` 都能装进去: ``` C++ template <> class TypeToID; @@ -1083,7 +1083,7 @@ void PrintID() 嗯,它输出的是-1。我们顺藤摸瓜会看到, `TypeToID`的类模板“原型”的ID是值就是-1。通过这个例子可以知道,当模板实例化时提供的模板参数不能匹配到任何的特化形式的时候,它就会去匹配类模板的“原型”形式。 -不过这里有一个问题要厘清一下。和继承不同,类模板的“原型”和它的特化类在实现上是没有关系的,并不是在类模板中写了 `ID` 这个Member,那所有的特化就必须要加入 `ID` 这个Member,或者特化就自动有了这个成员。完全没这回事。我们把类模板改成以下形式,或许能看的更清楚一点: +不过这里有一个问题要理清一下。和继承不同,类模板的“原型”和它的特化类在实现上是没有关系的,并不是在类模板中写了 `ID` 这个Member,那所有的特化就必须要加入 `ID` 这个Member,或者特化就自动有了这个成员。完全没这回事。我们把类模板改成以下形式,或许能看的更清楚一点: ``` C++ template class TypeToID @@ -1100,9 +1100,9 @@ public: void PrintID() { - cout << "ID of float: " << TypeToID::ID << endl; // Print "1" + 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这个成员。 } ``` @@ -1138,13 +1138,13 @@ void copy(void* dst, void const* src, size_t elemSize, size_t elemCount, void (* template ``` -接下来,我们要写函数原型: +接下来,我们要写函数原型: ``` C++ void copy(?? dest, ?? src, size_t elemCount); ``` -这里的 `??` 要怎么写呢?既然我们有了模板类型参数T,那我们不如就按照经验,写 `T*` 看看。 +这里的 `??` 要怎么写呢?既然我们有了模板类型参数T,那我们不如就按照经验,写 `T*` 看看。 ``` C++ template @@ -1179,7 +1179,7 @@ public: }; ``` -最后写个例子来测试一下,看看我们的 `T*` 能不能搞定 `float*` +最后写个例子来测试一下,看看我们的 `T*` 能不能搞定 `float*`: ``` C++ void PrintID() @@ -1221,14 +1221,15 @@ OK,猜出来了吗,T是`float`。为什么呢?因为你用 `float *` 匹 template class RemovePointer { - // 啥都不干,你要放一个不是指针的类型进来,我就让你死的难看。 +public: + typedef T Resylt; // 如果放进来的不是一个指针,那么它就是我们要的结果。 }; template class RemovePointer // 祖传牛皮藓,专治各类指针 { public: - typedef T Result; + typedef T Result; // 正如我们刚刚讲的,去掉一层指针,把 T* 这里的 T 取出来。 }; void Foo() @@ -1238,7 +1239,23 @@ void Foo() } ``` -OK,如果这个时候,我需要给 `int*` 提供一个更加特殊的特化,那么我还得都多提供一个: +当然啦,这里我们实现的不算是真正的 `RemovePointer`,因为我们只去掉了一层指针。而如果传进来的是类似 `RemovePointer` 这样的东西呢?是的没错,去掉一层之后还是一个指针。`RemovePointer::Result` 应该是一个 `int*`,要怎么才能实现我们想要的呢?聪明的你一定能想到:只要像剥洋葱一样,一层一层一层地剥开,不就好了吗!相应地我们应该怎么实现呢?可以把 `RemovePointer` 的特化版本改成这样(当然如果有一些不明白的地方你可以暂时跳过,接着往下看,很快就会明白的): + +``` C++ +template +class RemovePointer +{ +public: + // 如果是传进来的是一个指针,我们就剥夺一层,直到指针形式不存在为止。 + // 例如 RemovePointer,Result 是 RemovePointer::Result, + // 而 RemovePointer::Result 又是 int,最终就变成了我们想要的 int,其它也是类似。 + typedef typename RemovePointer::Result Result; +}; +``` + +是的没错,这便是我们想要的 `RemovePointer` 的样子。类似的你还可以试着实现 `RemoveConst`, `AddPointer` 之类的东西。 + +OK,回到我们之前的话题,如果这个时候,我需要给 `int*` 提供一个更加特殊的特化,那么我还得多提供一个: ``` C++ // ... @@ -1254,7 +1271,7 @@ public: }; template <> // 嗯,int* 已经是个具体的不能再具体的类型了,所以模板不需要额外的类型参数了 -class TypeToID // 嗯,对int*的特化。在这里呢,要把int*整体看作一个类型。 +class TypeToID // 嗯,对int*的特化。在这里呢,要把int*整体看作一个类型 { public: static int const ID = 0x12345678; // 给一个缺心眼的ID @@ -1268,9 +1285,9 @@ void PrintID() 嗯,这个时候它会输出0x12345678的十进制(大概?)。 可能会有较真的人说,`int*` 去匹配 `T` 或者 `T*`,也是合法的。就和你说22岁以上能结婚,那24岁当然也能结婚一样。 -那为什么 `int*` 就会找 `int*`,`float *`因为没有合适的特化就去找 `T*`,更一般的就去找 `T` 呢?废话,有专门为你准备的东西的不用,人干事?这就是直觉。 +那为什么 `int*` 就会找 `int*`,`float *`因为没有合适的特化就去找 `T*`,更一般的就去找 `T` 呢?废话,有专门为你准备的东西你不用,非要自己找事?这就是直觉。 但是呢,直觉对付更加复杂的问题还是没用的(也不是没用,主要是你没这个直觉了)。我们要把这个直觉,转换成合理的规则——即模板的匹配规则。 -当然,这个匹配规则是对复杂问题用的,所以我们会到实在一眼看不出来的时候才会动用它。一开始我们只要把握:模板是从最特殊到最一般形式进行匹配就可以了。 +当然,这个匹配规则是对复杂问题用的,所以我们会到实在一眼看不出来的时候才会动用它。一开始我们只要把握:**模板是从最特殊到最一般形式进行匹配的** 就可以了。 ### 2.3 即用即推导 @@ -1286,9 +1303,9 @@ template struct X {}; template struct Y { - typedef X ReboundType; // 类型定义1 + typedef X ReboundType; // 类型定义1 typedef typename X::MemberType MemberType; // 类型定义2 - typedef UnknownType MemberType3; // 类型定义3 + typedef UnknownType MemberType3; // 类型定义3 void foo() { @@ -1313,7 +1330,7 @@ template struct Y #### 2.3.2 名称查找:I am who I am 在C++标准中对于“名称查找(name lookup)”这个高大上的名词的诠释,主要集中出现在三处。第一处是3.4节,标题名就叫“Name Lookup”;第二处在10.2节,继承关系中的名称查找;第三处在14.6节,名称解析(name resolution)。 -名称查找/名称解析,是编译器的基石。对编译原理稍有了解的人,都知道“符号表”的存在即重要意义。考虑一段最基本的C代码: +名称查找/名称解析,是编译器的基石。对编译原理稍有了解的人,都知道“符号表”的存在及重要意义。考虑一段最基本的C代码: ``` C int a = 0; int b; @@ -1426,7 +1443,7 @@ template struct X { 接下来,我们就来解决2.3.1节中留下的几个问题。 先看第四个问题。为什么MSVC中,模板函数的定义内不管填什么编译器都不报错?因为MSVC在分析模板中成员函数定义时没有做任何事情。至于为啥连“大王叫我来巡山”都能过得去,这是C++语法/语义分析的特殊性导致的。 -C++是个非常复杂的语言,以至于它的编译器,不可能通过词法-语法-语义多趟分析清晰分割。因为它的语义将会直接干扰到语法: +C++是个非常复杂的语言,以至于它的编译器,不可能通过词法-语法-语义多趟分析清晰分割,因为它的语义将会直接干扰到语法: ```C++ void foo(){ @@ -1734,7 +1751,7 @@ DoWork i; // (4) DoWork pf; // (5) ``` -首先,编译器分析(0), (1), (2)三句,得知(0)是模板的原型,(1),(2),(3)是模板(0)的特化或偏特化。我们假设有两个字典,第一个字典存储了模板原型,我们称之为`TemplateDict`。第二个字典`TemplateSpecDict`,存储了模板原型所对应的特化/偏特化形式。所以编译器在这几句时,可以视作 +首先,编译器分析(0), (1), (2)三句,得知(0)是模板的原型,(1),(2),(3)是模板(0)的特化或偏特化。我们假设有两个字典,第一个字典存储了模板原型,我们称之为`TemplateDict`。第二个字典`TemplateSpecDict`,存储了模板原型所对应的特化/偏特化形式。所以编译器在处理这几句时,可以视作 ```C++ // 以下为伪代码 @@ -1812,7 +1829,7 @@ X v8; >`type [i]`, `template-name `, `TT`, `TT`, `TT<>` -对于某些实例化,偏特化的选择并不是唯一的。比如v4的参数是``,能够匹配的就有三条规则,1,6和7。很显然,6还是比7好一些,因为能多匹配一个指针。但是1和6,就很难说清楚谁更好了。一个说明了两者类型相同;另外一个则说明了两者都是指针。所以在这里,编译器也没办法决定使用那个,只好爆出了编译器错误。 +对于某些实例化,偏特化的选择并不是唯一的。比如v4的参数是``,能够匹配的就有三条规则,1,6和7。很显然,6还是比7好一些,因为能多匹配一个指针。但是1和6,就很难说清楚谁更好了。一个说明了两者类型相同;另外一个则说明了两者都是指针。所以在这里,编译器也没办法决定使用那个,只好报出了编译器错误。 其他的示例可以先自己推测一下, 再去编译器上尝试一番:[`goo.gl/9UVzje`](http://goo.gl/9UVzje)。 @@ -1964,7 +1981,7 @@ template ::value> struct Sa } }; -template struct SafeDivide{ // 偏特化A +template struct SafeDivide{ // 偏特化A static T Do(T lhs, T rhs){ return lhs/rhs; } @@ -2106,7 +2123,7 @@ void foo(T t, typename U::type u) { } void callFoo() { - foo(5, 5.0); // T == int, typename U::type == X::type == int + foo(5, 5.0); // T == int, typename U::type == X::type == float } ``` @@ -2127,7 +2144,7 @@ void foo(T t, typename U::type u) { } void callFoo() { - foo(5, 5.0); // T == int, typename U::type == X::type == int + foo(5, 5.0); // T == int, typename U::type == X::type == float foo(5, 5.0); // ??? } ``` @@ -2165,7 +2182,7 @@ void foo(T t, typename U::type2 u) { // ... } void callFoo() { - foo(5, 5.0); // T == int, typename U::type == X::type == int + foo(5, 5.0); // T == int, typename U::type == X::type == float foo( 1, 1.0 ); // ??? } ``` @@ -2217,7 +2234,7 @@ void callFoo() { } ``` -那么 `foo( A() )` 虽然匹配 `foo(B const&)` 会失败,但是它起码能匹配 `foo(A const&)`,所以它是正确的;`foo( B() )` 能同时匹配两个函数原型,但是 `foo(B const&)` 要更好一些,因此它选择了这个原型。而 `foo( C() );` 因为两个函数都匹配失败(Failure)了,所以它找不到相应的原型,这时才会爆出一个编译器错误(Error)。 +那么 `foo( A() )` 虽然匹配 `foo(B const&)` 会失败,但是它起码能匹配 `foo(A const&)`,所以它是正确的;`foo( B() )` 能同时匹配两个函数原型,但是 `foo(B const&)` 要更好一些,因此它选择了这个原型。而 `foo( C() );` 因为两个函数都匹配失败(Failure)了,所以它找不到相应的原型,这时才会报出一个编译器错误(Error)。 所以到这里我们就明白了,在很多情况下,Failure is not an error。编译器在遇到Failure的时候,往往还需要尝试其他的可能性。 @@ -2511,7 +2528,7 @@ void doSomething() { template void foo(ArgT&& a); ``` -加入我们要限定ArgT只能是 float 的衍生类型,那么写成下面这个样子是不对的,它实际上只能接受 float 的右值引用。 +假如我们要限定ArgT只能是 float 的衍生类型,那么写成下面这个样子是不对的,它实际上只能接受 float 的右值引用。 ```C++ void foo(float&& a); From 7e1f81b26955f9c7f4cb38aa8cd4f8b0cd7ff1f9 Mon Sep 17 00:00:00 2001 From: quran Date: Sat, 19 Aug 2017 17:19:43 +0800 Subject: [PATCH 77/99] replace int to float in comment --- ReadMe.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 10af4f0..2c6a414 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -2106,7 +2106,7 @@ void foo(T t, typename U::type u) { } void callFoo() { - foo(5, 5.0); // T == int, typename U::type == X::type == int + foo(5, 5.0); // T == int, typename U::type == X::type == float } ``` @@ -2127,7 +2127,7 @@ void foo(T t, typename U::type u) { } void callFoo() { - foo(5, 5.0); // T == int, typename U::type == X::type == int + foo(5, 5.0); // T == int, typename U::type == X::type == float foo(5, 5.0); // ??? } ``` @@ -2165,7 +2165,7 @@ void foo(T t, typename U::type2 u) { // ... } void callFoo() { - foo(5, 5.0); // T == int, typename U::type == X::type == int + foo(5, 5.0); // T == int, typename U::type == X::type == float foo( 1, 1.0 ); // ??? } ``` From 6aba25b2fc26e123044724dbe22b51a95f00821f Mon Sep 17 00:00:00 2001 From: quran Date: Sat, 19 Aug 2017 17:22:08 +0800 Subject: [PATCH 78/99] =?UTF-8?q?=E5=8A=A0=E5=85=A5=20=20=E2=80=94>=20?= =?UTF-8?q?=E5=81=87=E5=A6=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 2c6a414..6bc92e7 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -2511,7 +2511,7 @@ void doSomething() { template void foo(ArgT&& a); ``` -加入我们要限定ArgT只能是 float 的衍生类型,那么写成下面这个样子是不对的,它实际上只能接受 float 的右值引用。 +假如我们要限定ArgT只能是 float 的衍生类型,那么写成下面这个样子是不对的,它实际上只能接受 float 的右值引用。 ```C++ void foo(float&& a); From 3e4acf6d1db422badc37a6de32c70354d5a780cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?PeiKai=20=5B=E8=A3=B4=E5=87=AF=5D?= Date: Wed, 17 Oct 2018 20:23:45 +0800 Subject: [PATCH 79/99] typo --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 10af4f0..5f84efe 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -557,7 +557,7 @@ template class E {}; // ERROR: 别闹!早说过只能是整数类型 技术的学习是一个登山的过程。第一章是最为平坦的山脚道路。而从这一章开始,则是正式的爬坡。无论是我写作还是你阅读,都需要付出比第一章更多的代价。那么问题就是,付出更多的精力学习模板是否值得? -这个问题很功利,但是一阵见血。因为技术的根本目的在于解决需求。那C++的模板能做什么? +这个问题很功利,但是一针见血。因为技术的根本目的在于解决需求。那C++的模板能做什么? 一个高(树)大(新)上(蜂)的回答是,C++里面的模板,犹如C中的宏、C#和Java中的自省(restropection)和反射(reflection)一样,是一个改变语言内涵,拓展语言外延的存在。 From af4b3d0d7e4159b7da768533fd25a1eb6b4c1201 Mon Sep 17 00:00:00 2001 From: YJieZhang <774780169@qq.com> Date: Mon, 29 Oct 2018 10:50:40 +0800 Subject: [PATCH 80/99] small fix for stack example --- ReadMe.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 5f84efe..de22db4 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -579,11 +579,11 @@ template class E {}; // ERROR: 别闹!早说过只能是整数类型 class StackInt { public: - void push(Int v); - Int pop(); - Int Find(Int x) + void push(int v); + int pop(); + int Find(int x) { - for(Int i = 1; i <= size; ) + for(int i = 0; i < size; ++i) { if(data[i] == x) { return i; } } @@ -598,11 +598,11 @@ public: class StackFloat { public: - void push(Float v); - Float pop(); - Int Find(Float x) + void push(float v); + float pop(); + float Find(float x) { - for(Int i = 1; i <= size; ) + for(int i = 0; i < size; ++i) { if(data[i] == x) { return i; } } @@ -634,9 +634,9 @@ class Stack public: void push(T v); T pop(); - Int Find(T x) + T Find(T x) { - for(Int i = 0; i <= size; ++i) + for(int i = 0; i < size; ++i) { if(data[i] == x) { return i; } } From 5723b1bf1e24333a6f99a9fc68b128095ac27f1e Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 29 Oct 2019 10:28:24 -0400 Subject: [PATCH 81/99] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BE=8B=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 例中的find应该返回元素的index,所以其签名应该是 `int Find(T x)` 而不是`T Find(T x)` --- ReadMe.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index f5b4a63..5f69bd6 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -600,7 +600,7 @@ class StackFloat public: void push(float v); float pop(); - float Find(float x) + int Find(float x) { for(int i = 0; i < size; ++i) { @@ -634,7 +634,7 @@ class Stack public: void push(T v); T pop(); - T Find(T x) + int Find(T x) { for(int i = 0; i < size; ++i) { From 473d64aeaaea82970741fb63e9e91787ad679414 Mon Sep 17 00:00:00 2001 From: mapleFU <1506118561@qq.com> Date: Sun, 24 Nov 2019 01:02:59 +0800 Subject: [PATCH 82/99] [ADD] add std:: in code samples --- ReadMe.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 5f69bd6..fa2eac7 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -2384,7 +2384,7 @@ template void inc_counter(T& intTypeCounter); template void inc_counter( T& counterObj, typename std::enable_if< - is_base_of::value + std::is_base_of::value >::type* = nullptr ); template void inc_counter( @@ -2558,7 +2558,7 @@ template void foo( ArgT&& a, typename std::enabled_if< - is_same, float>::value + std::is_same, float>::value >::type* = nullptr ); ``` From b104959a1bca6eaa06a0ba96a7cefbef938d9e27 Mon Sep 17 00:00:00 2001 From: mapleFU <1506118561@qq.com> Date: Sun, 24 Nov 2019 01:16:48 +0800 Subject: [PATCH 83/99] [Fix] Fixing 'is-base-of' --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index fa2eac7..db003b7 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -2384,7 +2384,7 @@ template void inc_counter(T& intTypeCounter); template void inc_counter( T& counterObj, typename std::enable_if< - std::is_base_of::value + std::is_base_of::value >::type* = nullptr ); template void inc_counter( From da0f94a7207267fae2cad26e472000a2016d15f1 Mon Sep 17 00:00:00 2001 From: Fang Date: Fri, 3 Jan 2020 17:58:58 -0500 Subject: [PATCH 84/99] Fix some typos --- ReadMe.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index db003b7..5538d8b 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -979,7 +979,7 @@ void foo() template class AddFloatOrMulInt; // 但是这个类,是给T是Int的时候用的,于是我们写作 -class AddFloatOrMulInt +class AddFloatOrMulInt; // 当然,这里编译是通不过的。 // 但是它又不是个普通类,而是类模板的一个特化(特例)。 @@ -992,7 +992,7 @@ template class AddFloatOrMulInt; template <> class AddFloatOrMulInt { // ... 针对Int的实现 ... -} +}; // Bingo! ``` @@ -1222,7 +1222,7 @@ template class RemovePointer { public: - typedef T Resylt; // 如果放进来的不是一个指针,那么它就是我们要的结果。 + typedef T Result; // 如果放进来的不是一个指针,那么它就是我们要的结果。 }; template @@ -1340,9 +1340,9 @@ printf("Result: %d", b); 在这段代码中,所有出现的符号可以分为以下几类: * `int`:类型标识符,代表整型; -* `a`,`b`,`printf`:变量名或函数名; -* `=`,`+`,`*`:运算符; -* `,`,`;`,`(`,`)`:分隔符; +* `a`, `b`, `printf`:变量名或函数名; +* `=`, `+`, `*`:运算符; +* `,`, `;`, `(`, `)`:分隔符; 那么,编译器怎么知道`int`就是整数类型,`b=(a+1)*2`中的`a`和`b`就是整型变量呢?这就是名称查找/名称解析的作用:它告诉编译器,这个标识符(identifer)是在哪里被声明或定义的,它究竟是什么意思。 @@ -1475,7 +1475,7 @@ X xf; 此时如果X中有一些与模板参数无关的错误,如果名称查找/语义分析在两个阶段完成,那么这些错误会很早、且唯一的被提示出来;但是如果一切都在实例化时处理,那么可能会导致不同的实例化过程提示同样的错误。而模板在运用过程中,往往会产生很多实例,此时便会大量报告同样的错误。 当然,MSVC并不会真的这么做。根据推测,最终他们是合并了相同的错误。因为即便对于模板参数相关的编译错误,也只能看到最后一次实例化的错误信息: -``` +```C++ template struct X {}; template struct Y @@ -1834,7 +1834,7 @@ X v8; > 令`T`是模板类型实参或者类型列表(如 _int, float, double_ 这样的,`TT`是template-template实参(参见6.2节),`i`是模板的非类型参数(整数、指针等),则以下形式的形参都会参与匹配: -> `T`,`cv-list T`,`T*`, `template-name `, `T&`, `T&&` +> `T`, `cv-list T`, `T*`, `template-name `, `T&`, `T&&` >`T [ integer-constant ]` @@ -2565,7 +2565,7 @@ void foo( 从上面这些例子可以看到,SFINAE最主要的作用,是保证编译器在泛型函数、偏特化、及一般重载函数中遴选函数原型的候选列表时不被打断。除此之外,它还有一个很重要的元编程作用就是实现部分的编译期自省和反射。 -虽然它写起来并不直观,但是对于既没有编译器自省、也没有Concept的C++1y来说,已经是最好的选择了。 +虽然它写起来并不直观,但是对于既没有编译器自省、也没有Concept的C++11来说,已经是最好的选择了。 (补充例子:构造函数上的enable_if) From 8254a9edfdae2b85bb2a2dd0124c38e0debcb05a Mon Sep 17 00:00:00 2001 From: CanftIn Date: Fri, 10 Apr 2020 12:17:30 +0800 Subject: [PATCH 85/99] Typo fix --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 5538d8b..76e0c3b 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -2290,7 +2290,7 @@ functionName ( ```C++ template < typename T, - typenname U = typename vector::iterator // 1 + typename U = typename vector::iterator // 1 > typename vector::value_type // 1 foo( From eff5cd2eebd5729cb7273533ef5779da72e4b899 Mon Sep 17 00:00:00 2001 From: CanftIn Date: Fri, 10 Apr 2020 12:39:42 +0800 Subject: [PATCH 86/99] Format indent to 4 spaces --- ReadMe.md | 226 +++++++++++++++++++++++++++--------------------------- 1 file changed, 113 insertions(+), 113 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 76e0c3b..fd4412f 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -105,7 +105,7 @@ Template Class定义: ```C++ template class ClassA { - T member; + T member; }; ``` @@ -124,7 +124,7 @@ void foo(int a); ``` C++ // 注意:这并不是有效的C++语法,只是为了说明模板的作用 typedef class { - int member; + int member; } ClassA; ``` @@ -139,11 +139,11 @@ template class vector { public: - void push_back(T const&); - void clear(); + void push_back(T const&); + void clear(); private: - T* elements; + T* elements; }; ``` @@ -225,7 +225,7 @@ private: template void vector::clear() // 函数的实现放在这里 { - // Function body + // Function body } ``` @@ -668,11 +668,11 @@ Float : VInt64Mul(floatx2, floatx2) for(v4a, v4b : vectorsA, vectorsB) { if type is Int8, Int16 - VInt32Mul( ConvertToInt32(v4a), ConvertToInt32(v4b) ) - elif type is Int32 - VInt32Mul( v4a, v4b ) - elif type is Float - ... + VInt32Mul( ConvertToInt32(v4a), ConvertToInt32(v4b) ) + elif type is Int32 + VInt32Mul( v4a, v4b ) + elif type is Float + ... } ``` @@ -1175,7 +1175,7 @@ template // 嗯,需要一个T class TypeToID // 我要对所有的指针类型特化,所以这里就写T* { public: - static int const ID = 0x80000000; // 用最高位表示它是一个指针 + static int const ID = 0x80000000; // 用最高位表示它是一个指针 }; ``` @@ -1205,7 +1205,7 @@ public: void PrintID() { - cout << "ID of float*: " << TypeToID< TypeToID::SameAsT >::ID << endl; + cout << "ID of float*: " << TypeToID< TypeToID::SameAsT >::ID << endl; } ``` @@ -1461,7 +1461,7 @@ void foo(){ // ----------- X.h ------------ template struct X { - // 实现代码 + // 实现代码 }; // ---------- X.cpp ----------- @@ -2123,7 +2123,7 @@ void foo(){ ``` C++ template void foo(T t, typename U::type u) { - // ... + // ... } ``` @@ -2131,16 +2131,16 @@ void foo(T t, typename U::type u) { ``` C++ struct X { - typedef float type; + typedef float type; }; template void foo(T t, typename U::type u) { - // ... + // ... } void callFoo() { - foo(5, 5.0); // T == int, typename U::type == X::type == float + foo(5, 5.0); // T == int, typename U::type == X::type == float } ``` @@ -2148,11 +2148,11 @@ void callFoo() { ```C++ struct X { - typedef float type; + typedef float type; }; struct Y { - typedef float type2; + typedef float type2; }; template @@ -2161,8 +2161,8 @@ void foo(T t, typename U::type u) { } void callFoo() { - foo(5, 5.0); // T == int, typename U::type == X::type == float - foo(5, 5.0); // ??? + foo(5, 5.0); // T == int, typename U::type == X::type == float + foo(5, 5.0); // ??? } ``` @@ -2182,16 +2182,16 @@ error: no matching function for call to 'foo' ```C++ struct X { - typedef float type; + typedef float type; }; struct Y { - typedef float type2; + typedef float type2; }; template void foo(T t, typename U::type u) { - // ... + // ... } template @@ -2199,8 +2199,8 @@ void foo(T t, typename U::type2 u) { // ... } void callFoo() { - foo(5, 5.0); // T == int, typename U::type == X::type == float - foo( 1, 1.0 ); // ??? + foo(5, 5.0); // T == int, typename U::type == X::type == float + foo( 1, 1.0 ); // ??? } ``` @@ -2245,9 +2245,9 @@ void foo(A const&) {} void foo(B const&) {} void callFoo() { - foo( A() ); - foo( B() ); - foo( C() ); + foo( A() ); + foo( B() ); + foo( C() ); } ``` @@ -2271,17 +2271,17 @@ void callFoo() { ```C++ template < - typename T0, - // 一大坨其他模板参数 - typename U = /* 和前面T有关的一大坨 */ + typename T0, + // 一大坨其他模板参数 + typename U = /* 和前面T有关的一大坨 */ > RType /* 和模板参数有关的一大坨 */ functionName ( - PType0 /* PType0 是和模板参数有关的一大坨 */, - PType1 /* PType1 是和模板参数有关的一大坨 */, - // ... 其他参数 + PType0 /* PType0 是和模板参数有关的一大坨 */, + PType1 /* PType1 是和模板参数有关的一大坨 */, + // ... 其他参数 ) { - // 实现,和模板参数有关的一大坨 + // 实现,和模板参数有关的一大坨 } ``` @@ -2289,19 +2289,19 @@ functionName ( ```C++ template < - typename T, - typename U = typename vector::iterator // 1 + typename T, + typename U = typename vector::iterator // 1 > typename vector::value_type // 1 - foo( - T*, // 1 - T&, // 1 - typename T::internal_type, // 1 - typename add_reference::type, // 1 - int // 这里都不需要 substitution - ) +foo( + T*, // 1 + T&, // 1 + typename T::internal_type, // 1 + typename add_reference::type, // 1 + int // 这里都不需要 substitution +) { - // 整个实现部分,都没有 substitution。这个很关键。 + // 整个实现部分,都没有 substitution。这个很关键。 } ``` @@ -2311,11 +2311,11 @@ typename vector::value_type // 1 ```C++ struct X { - typedef int type; + typedef int type; }; struct Y { - typedef int type2; + typedef int type2; }; template void foo(typename T::type); // Foo0 @@ -2323,9 +2323,9 @@ template void foo(typename T::type2); // Foo1 template void foo(T); // Foo2 void callFoo() { - foo(5); // Foo0: Succeed, Foo1: Failed, Foo2: Failed - foo(10); // Foo0: Failed, Foo1: Succeed, Foo2: Failed - foo(15); // Foo0: Failed, Foo1: Failed, Foo2: Succeed + foo(5); // Foo0: Succeed, Foo1: Failed, Foo2: Failed + foo(10); // Foo0: Failed, Foo1: Succeed, Foo2: Failed + foo(15); // Foo0: Failed, Foo1: Failed, Foo2: Succeed } ``` @@ -2337,33 +2337,33 @@ std/boost库中的 `enable_if` 是 SFINAE 最直接也是最主要的应用。 ```C++ struct ICounter { - virtual void increase() = 0; - virtual ~ICounter() {} + virtual void increase() = 0; + virtual ~ICounter() {} }; struct Counter: public ICounter { - void increase() override { - // Implements - } + void increase() override { + // Implements + } }; template void inc_counter(T& counterObj) { - counterObj.increase(); + counterObj.increase(); } template void inc_counter(T& intTypeCounter){ - ++intTypeCounter; + ++intTypeCounter; } void doSomething() { - Counter cntObj; - uint32_t cntUI32; + Counter cntObj; + uint32_t cntUI32; - // blah blah blah - inc_counter(cntObj); - inc_counter(cntUI32); + // blah blah blah + inc_counter(cntObj); + inc_counter(cntUI32); } ``` @@ -2382,16 +2382,16 @@ template void inc_counter(T& intTypeCounter); ```C++ template void inc_counter( - T& counterObj, - typename std::enable_if< - std::is_base_of::value - >::type* = nullptr ); + T& counterObj, + typename std::enable_if< + std::is_base_of::value + >::type* = nullptr ); template void inc_counter( - T& counterInt, - typename std::enable_if< - std::is_integral::value - >::type* = nullptr ); + T& counterInt, + typename std::enable_if< + std::is_integral::value + >::type* = nullptr ); ``` 然后我们解释一下,这个 `enable_if` 是怎么工作的,语法为什么这么丑: @@ -2426,9 +2426,9 @@ void inc_counter(ICounter& counterObj); ```C++ struct ICounter {}; struct Counter: public ICounter { - void increase() { - // impl - } + void increase() { + // impl + } }; ``` @@ -2442,13 +2442,13 @@ template void inc_counter(T& c) { ++c; }; void doSomething() { - Counter cntObj; - uint32_t cntUI32; + Counter cntObj; + uint32_t cntUI32; - // blah blah blah - inc_counter(cntObj); // 1 - inc_counter(static_cast(cntObj)); // 2 - inc_counter(cntUI32); // 3 + // blah blah blah + inc_counter(cntObj); // 1 + inc_counter(static_cast(cntObj)); // 2 + inc_counter(cntUI32); // 3 } ``` @@ -2466,34 +2466,34 @@ void doSomething() { struct ICounter {}; struct Counter: public ICounter { - void increase() { - // impl - } + void increase() { + // impl + } }; template void inc_counter( - T& counterObj, - typename std::enable_if< - std::is_base_of::value - >::type* = nullptr ){ - counterObj.increase(); + T& counterObj, + typename std::enable_if< + std::is_base_of::value + >::type* = nullptr ){ + counterObj.increase(); } template void inc_counter( - T& counterInt, - typename std::enable_if< - std::is_integral::value - >::type* = nullptr ){ - ++counterInt; + T& counterInt, + typename std::enable_if< + std::is_integral::value + >::type* = nullptr ){ + ++counterInt; } void doSomething() { - Counter cntObj; - uint32_t cntUI32; + Counter cntObj; + uint32_t cntUI32; - // blah blah blah - inc_counter(cntObj); // OK! - inc_counter(cntUI32); // OK! + // blah blah blah + inc_counter(cntObj); // OK! + inc_counter(cntUI32); // OK! } ``` @@ -2513,28 +2513,28 @@ template void foo(T& c, decltype(c.increase())* = nullptr); ```C++ struct Counter { - void increase() { - // Implements - } + void increase() { + // Implements + } }; template void inc_counter(T& intTypeCounter, std::decay_t* = nullptr) { - ++intTypeCounter; + ++intTypeCounter; } template void inc_counter(T& counterObj, std::decay_t* = nullptr) { - counterObj.increase(); + counterObj.increase(); } void doSomething() { - Counter cntObj; - uint32_t cntUI32; + Counter cntObj; + uint32_t cntUI32; - // blah blah blah - inc_counter(cntObj); - inc_counter(cntUI32); + // blah blah blah + inc_counter(cntObj); + inc_counter(cntUI32); } ``` @@ -2556,10 +2556,10 @@ void foo(float&& a); ```C++ template void foo( - ArgT&& a, - typename std::enabled_if< - std::is_same, float>::value - >::type* = nullptr + ArgT&& a, + typename std::enabled_if< + std::is_same, float>::value + >::type* = nullptr ); ``` From 9af69e3b34d495c4628b180d5e755277997590c5 Mon Sep 17 00:00:00 2001 From: Ye Wu Date: Sat, 25 Apr 2020 15:55:49 -0700 Subject: [PATCH 87/99] Add TOC --- ReadMe.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index fd4412f..80448a0 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,6 +1,7 @@ - # C++ Template 进阶指南 +autoauto- [C++ Template 进阶指南](#c-template-进阶指南)auto - [0. 前言](#0-前言)auto - [0.1 C++另类简介:比你用的复杂,但比你想的简单](#01-c另类简介比你用的复杂但比你想的简单)auto - [0.2 适宜读者群](#02-适宜读者群)auto - [0.3 版权](#03-版权)auto - [0.4 推荐编译环境](#04-推荐编译环境)auto - [0.5 体例](#05-体例)auto - [0.5.1 示例代码](#051-示例代码)auto - [0.5.2 引用](#052-引用)auto - [0.6 意见、建议、喷、补遗、写作计划](#06-意见建议喷补遗写作计划)auto - [1. Template的基本语法](#1-template的基本语法)auto - [1.1 Template Class基本语法](#11-template-class基本语法)auto - [1.1.1 Template Class的与成员变量定义](#111-template-class的与成员变量定义)auto - [1.1.2 模板的使用](#112-模板的使用)auto - [1.1.3 模板类的成员函数定义](#113-模板类的成员函数定义)auto - [1.2 Template Function的基本语法](#12-template-function的基本语法)auto - [1.2.1 Template Function的声明和定义](#121-template-function的声明和定义)auto - [1.2.2 模板函数的使用](#122-模板函数的使用)auto - [1.3 整型也可是Template参数](#13-整型也可是template参数)auto - [1.4 模板形式与功能是统一的](#14-模板形式与功能是统一的)auto - [2. 模板元编程基础](#2--模板元编程基础)auto - [2.1 编程,元编程,模板元编程](#21-编程元编程模板元编程)auto - [2.2 模板世界的If-Then-Else:类模板的特化与偏特化](#22-模板世界的if-then-else类模板的特化与偏特化)auto - [2.2.1 根据类型执行代码](#221-根据类型执行代码)auto - [2.2.2 特化](#222-特化)auto - [2.2.3 特化:一些其它问题](#223-特化一些其它问题)auto - [2.3 即用即推导](#23-即用即推导)auto - [2.3.1 视若无睹的语法错误](#231-视若无睹的语法错误)auto - [2.3.2 名称查找:I am who I am](#232-名称查找i-am-who-i-am)auto - [2.3.3 “多余的” typename 关键字](#233-多余的--typename-关键字)auto - [2.4 本章小结](#24-本章小结)auto - [3 深入理解特化与偏特化](#3---深入理解特化与偏特化)auto - [3.1 正确的理解偏特化](#31-正确的理解偏特化)auto - [3.1.1 偏特化与函数重载的比较](#311-偏特化与函数重载的比较)auto - [3.1.2 不定长的模板参数](#312-不定长的模板参数)auto - [3.1.3 模板的默认实参](#313-模板的默认实参)auto - [3.2 后悔药:SFINAE](#32-后悔药sfinae)auto - [4 元编程下的数据结构与算法](#4-元编程下的数据结构与算法)auto - [4.1 表达式与数值计算](#41-表达式与数值计算)auto - [4.1 获得类型的属性——类型萃取(Type Traits)](#41-获得类型的属性类型萃取type-traits)auto - [4.2 列表与数组](#42-列表与数组)auto - [4.3 字典结构](#43-字典结构)auto - [4.4 “快速”排序](#44-快速排序)auto - [4.5 其它常用的“轮子”](#45-其它常用的轮子)auto - [5 模板的进阶技巧](#5-模板的进阶技巧)auto - [5.1 嵌入类](#51-嵌入类)auto - [5.2 Template-Template Class](#52-template-template-class)auto - [5.3 高阶函数](#53-高阶函数)auto - [5.4 闭包:模板的“基于对象”](#54-闭包模板的基于对象)auto - [5.5 占位符(placeholder):在C++中实现方言的基石](#55-占位符placeholder在c中实现方言的基石)auto - [5.6 编译期“多态”](#56-编译期多态)auto - [6 模板的威力:从foreach, transform到Linq](#6---模板的威力从foreach-transform到linq)auto - [6.1 Foreach与Transform](#61-foreach与transform)auto - [6.2 Boost中的模板](#62-boost中的模板)auto - [6.3 Reactor、Linq与C++中的实践](#63-reactorlinq与c中的实践)auto - [6.4 更高更快更强:从Linq到FP](#64-更高更快更强从linq到fp)auto - [7 结语:讨论有益,争端无用](#7---结语讨论有益争端无用)auto - [7.1 更好的编译器,更友善的出错信息](#71-更好的编译器更友善的出错信息)auto - [7.2 模板的症结:易于实现,难于完美](#72-模板的症结易于实现难于完美)auto - [7.3 一些期望](#73-一些期望)autoauto + ## 0. 前言 ### 0.1 C++另类简介:比你用的复杂,但比你想的简单 From 4808f7ca536bf2a7c1da882aab54840f357ec762 Mon Sep 17 00:00:00 2001 From: Ye Wu Date: Sat, 25 Apr 2020 15:59:29 -0700 Subject: [PATCH 88/99] Update TOC. --- ReadMe.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 80448a0..02d1f09 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,6 +1,65 @@ # C++ Template 进阶指南 -autoauto- [C++ Template 进阶指南](#c-template-进阶指南)auto - [0. 前言](#0-前言)auto - [0.1 C++另类简介:比你用的复杂,但比你想的简单](#01-c另类简介比你用的复杂但比你想的简单)auto - [0.2 适宜读者群](#02-适宜读者群)auto - [0.3 版权](#03-版权)auto - [0.4 推荐编译环境](#04-推荐编译环境)auto - [0.5 体例](#05-体例)auto - [0.5.1 示例代码](#051-示例代码)auto - [0.5.2 引用](#052-引用)auto - [0.6 意见、建议、喷、补遗、写作计划](#06-意见建议喷补遗写作计划)auto - [1. Template的基本语法](#1-template的基本语法)auto - [1.1 Template Class基本语法](#11-template-class基本语法)auto - [1.1.1 Template Class的与成员变量定义](#111-template-class的与成员变量定义)auto - [1.1.2 模板的使用](#112-模板的使用)auto - [1.1.3 模板类的成员函数定义](#113-模板类的成员函数定义)auto - [1.2 Template Function的基本语法](#12-template-function的基本语法)auto - [1.2.1 Template Function的声明和定义](#121-template-function的声明和定义)auto - [1.2.2 模板函数的使用](#122-模板函数的使用)auto - [1.3 整型也可是Template参数](#13-整型也可是template参数)auto - [1.4 模板形式与功能是统一的](#14-模板形式与功能是统一的)auto - [2. 模板元编程基础](#2--模板元编程基础)auto - [2.1 编程,元编程,模板元编程](#21-编程元编程模板元编程)auto - [2.2 模板世界的If-Then-Else:类模板的特化与偏特化](#22-模板世界的if-then-else类模板的特化与偏特化)auto - [2.2.1 根据类型执行代码](#221-根据类型执行代码)auto - [2.2.2 特化](#222-特化)auto - [2.2.3 特化:一些其它问题](#223-特化一些其它问题)auto - [2.3 即用即推导](#23-即用即推导)auto - [2.3.1 视若无睹的语法错误](#231-视若无睹的语法错误)auto - [2.3.2 名称查找:I am who I am](#232-名称查找i-am-who-i-am)auto - [2.3.3 “多余的” typename 关键字](#233-多余的--typename-关键字)auto - [2.4 本章小结](#24-本章小结)auto - [3 深入理解特化与偏特化](#3---深入理解特化与偏特化)auto - [3.1 正确的理解偏特化](#31-正确的理解偏特化)auto - [3.1.1 偏特化与函数重载的比较](#311-偏特化与函数重载的比较)auto - [3.1.2 不定长的模板参数](#312-不定长的模板参数)auto - [3.1.3 模板的默认实参](#313-模板的默认实参)auto - [3.2 后悔药:SFINAE](#32-后悔药sfinae)auto - [4 元编程下的数据结构与算法](#4-元编程下的数据结构与算法)auto - [4.1 表达式与数值计算](#41-表达式与数值计算)auto - [4.1 获得类型的属性——类型萃取(Type Traits)](#41-获得类型的属性类型萃取type-traits)auto - [4.2 列表与数组](#42-列表与数组)auto - [4.3 字典结构](#43-字典结构)auto - [4.4 “快速”排序](#44-快速排序)auto - [4.5 其它常用的“轮子”](#45-其它常用的轮子)auto - [5 模板的进阶技巧](#5-模板的进阶技巧)auto - [5.1 嵌入类](#51-嵌入类)auto - [5.2 Template-Template Class](#52-template-template-class)auto - [5.3 高阶函数](#53-高阶函数)auto - [5.4 闭包:模板的“基于对象”](#54-闭包模板的基于对象)auto - [5.5 占位符(placeholder):在C++中实现方言的基石](#55-占位符placeholder在c中实现方言的基石)auto - [5.6 编译期“多态”](#56-编译期多态)auto - [6 模板的威力:从foreach, transform到Linq](#6---模板的威力从foreach-transform到linq)auto - [6.1 Foreach与Transform](#61-foreach与transform)auto - [6.2 Boost中的模板](#62-boost中的模板)auto - [6.3 Reactor、Linq与C++中的实践](#63-reactorlinq与c中的实践)auto - [6.4 更高更快更强:从Linq到FP](#64-更高更快更强从linq到fp)auto - [7 结语:讨论有益,争端无用](#7---结语讨论有益争端无用)auto - [7.1 更好的编译器,更友善的出错信息](#71-更好的编译器更友善的出错信息)auto - [7.2 模板的症结:易于实现,难于完美](#72-模板的症结易于实现难于完美)auto - [7.3 一些期望](#73-一些期望)autoauto +- [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) + - [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. 前言 From c8c3cdde8fd4bbfc969d25c6fa81b653f6d65483 Mon Sep 17 00:00:00 2001 From: Ye Wu Date: Sat, 25 Apr 2020 16:03:04 -0700 Subject: [PATCH 89/99] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E6=9C=AA?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=9A=84=E7=AB=A0=E8=8A=82=E7=9A=84=E5=88=86?= =?UTF-8?q?=E5=89=B2=E8=AE=B0=E5=8F=B7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index 02d1f09..ffd6791 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -37,6 +37,7 @@ - [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) @@ -2629,6 +2630,8 @@ void foo( (补充例子:构造函数上的enable_if) +## !!! 以下章节未完成 !!! + ## 4 元编程下的数据结构与算法 ### 4.1 表达式与数值计算 ### 4.1 获得类型的属性——类型萃取(Type Traits) From 6cd559f3aa577211d25dd80f33fd719ce14a0523 Mon Sep 17 00:00:00 2001 From: Ye Wu Date: Sat, 25 Apr 2020 16:09:03 -0700 Subject: [PATCH 90/99] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E6=89=80?= =?UTF-8?q?=E7=94=A8=E5=B7=A5=E5=85=B7=E5=A3=B0=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ReadMe.md b/ReadMe.md index ffd6791..9eb47bf 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,5 +1,6 @@ # 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) From 7ba2eff1e1bc4c1683f513697337fc7be3b7fd37 Mon Sep 17 00:00:00 2001 From: zc Date: Sun, 24 May 2020 17:26:29 +0800 Subject: [PATCH 91/99] Fix typo. --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index 9eb47bf..5ff32dc 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -442,7 +442,7 @@ int result = Add(a, b); error C2782: 'T _1_2_2::Add(T,T)' : template parameter 'T' is ambiguous ``` -好吧,"ambigous",这个提示再明确不过了。 +好吧,"ambiguous",这个提示再明确不过了。 不过,只要你别逼得编译器精神分裂的话,编译器其实是非常聪明的,它可以从很多的蛛丝马迹中,猜测到你真正的意图,有如下面的例子: From b306167a0e7d8218dc814a61ce5d8b42f7b2867d Mon Sep 17 00:00:00 2001 From: nmreadelf <7260482+nmreadelf@users.noreply.github.com> Date: Wed, 15 Dec 2021 18:05:34 +0800 Subject: [PATCH 92/99] add public keyword `float` `void*` typeid example `float` and `void*` typeid exmaple add public keywork --- ReadMe.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ReadMe.md b/ReadMe.md index 5ff32dc..d0e28d2 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1099,6 +1099,7 @@ void PrintID() template <> class TypeToID { +public: static int const ID = 0xF10A7; }; ``` @@ -1108,6 +1109,7 @@ template <> class TypeToID ``` C++ template <> class TypeToID { +public: static int const ID = 0x401d; }; From 6282afe56c7770acf19cb32db8f00f91e030bd1f Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Mon, 19 Sep 2022 19:58:18 -0700 Subject: [PATCH 93/99] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20concept=20=E7=9A=84?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 1f4c65a..1e009f6 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -2365,7 +2365,9 @@ foo( int // 这里都不需要 substitution ) { - // 整个实现部分,都没有 substitution。这个很关键。 + // 根据定义,substitution只发生在函数签名上。 + // 故而整个函数实现部分都不会存在 substitution。 + // 这是一个重点需要记住。 } ``` @@ -2631,7 +2633,105 @@ void foo( 虽然它写起来并不直观,但是对于既没有编译器自省、也没有Concept的C++11来说,已经是最好的选择了。 -(补充例子:构造函数上的enable_if) +### Concept “概念” + +#### “概念” 解决了什么问题 +从上一节可以看出,我们兜兜转转了那么久,就是为了解决两个问题: + +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作为keyword是存在二义性的。当它用于模板函数或者模板类的声明时,它是一个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; }; +``` + +虽然看起来要更长一点,但是对于复杂类型来说,还是会友善许多。以后会找个例子给大家陈述。 + ## !!! 以下章节未完成 !!! @@ -2643,6 +2743,8 @@ void foo( ### 4.4 “快速”排序 ### 4.5 其它常用的“轮子” +## 非模板的编译期计算 + ## 5 模板的进阶技巧 ### 5.1 嵌入类 ### 5.2 Template-Template Class From f8a18d1db213a7dd21ca7ae712d62f4626b05df8 Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Tue, 20 Sep 2022 05:21:17 -0700 Subject: [PATCH 94/99] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=88=B0=E6=AD=A3?= =?UTF-8?q?=E5=9C=A8=E7=94=A8=E7=9A=84=E7=BC=96=E8=AF=91=E5=99=A8=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 1e009f6..79c00a7 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -105,9 +105,8 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言, 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/)。 From f3e61e2012f08b971b3e393ab978717d439acf9c Mon Sep 17 00:00:00 2001 From: wuye9036 Date: Tue, 27 Sep 2022 19:32:13 -0700 Subject: [PATCH 95/99] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BA=86=E8=A1=8C?= =?UTF-8?q?=E5=86=85=E7=9A=84=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 79c00a7..13f422c 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -2683,7 +2683,7 @@ void inc_counter(T& intTypeCounter) { ++intTypeCounter; } ``` -直接告诉编译器,我们对T的要求是你得有++。 +直接告诉编译器,我们对T的要求是你得有`++`。 当然有人会问,那能不能直接写成以下形式,不是更简单吗 @@ -2693,7 +2693,7 @@ void inc_counter(T& cnt); ``` 答案是不能。 -因为requires作为keyword是存在二义性的。当它用于模板函数或者模板类的声明时,它是一个constraint,后面需要跟着concept表达式;而用于concept中,则是一个required expression,用于concept的求解。既然constraint后面跟着一个concept表达式,而requires也可以用来定义一个concept expression,那么一个风骚的想法形成了:我能不能用 requires (requires (T t) {++t;}) 来约束模板函数的类型呢? +因为`requires`作为keyword是存在二义性的。当它用于模板函数或者模板类的声明时,它是一个constraint,后面需要跟着concept表达式;而用于concept中,则是一个required expression,用于concept的求解。既然constraint后面跟着一个concept表达式,而requires也可以用来定义一个concept expression,那么一个风骚的想法形成了:我能不能用 `requires (requires (T t) {++t;})` 来约束模板函数的类型呢? 当然是可以的!C++就是这么的简(~~有~~)单(~~病~~)! From c0e945d76433e7892a4c656ead795db0e15ab769 Mon Sep 17 00:00:00 2001 From: Ye Wu Date: Tue, 11 Oct 2022 18:55:09 -0700 Subject: [PATCH 96/99] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86TOC=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BA=86=E9=83=A8=E5=88=86=E8=A1=8C=E6=96=87?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 288 ++++++++++++++++++++++++++---------------------------- 1 file changed, 140 insertions(+), 148 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 13f422c..c6c0cd1 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 Tempalte) 入门](#23-函数模板-function-tempalte-入门) + - [2.3.1. Function Template 的声明和定义](#231-function-template-的声明和定义) + - [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++入门,例子中也多少会牵涉一些其它知识,因此如果读者能够具备以下条件,会读起来更加轻松: @@ -95,13 +77,13 @@ C++之所以变成一门层次丰富、结构多变、语法繁冗的语言, 诚如上节所述,本文并不是《C++ Templates》的简单重复,与《Modern C++ Design》交叠更少。从知识结构上,我建议大家可以先读本文,再阅读《C++ Templates》获取更丰富的语法与实现细节,以更进一步;《Modern C++ Design》除了元编程之外,还有很多的泛型编程示例,原则上泛型编程的部分与我所述的内容交叉不大,读者在读完1-3章了解模板的基本规则之后便可阅读《MCD》的相应章节;元编程部分(如Typelist)建议在阅读完本文之后再行阅读,或许会更易理解。 -### 0.3 版权 +## 1.3. 版权 本文是随写随即同步到Github上,因此在行文中难免会遗漏引用。本文绝大部分内容应是直接承出我笔,但是也不定会有他山之石。所有指涉内容我会尽量以引号框记,或在上下文和边角注记中标示,如有遗漏烦请不吝指出。 全文所有为我所撰写的部分,作者均保留所有版权。如果有需要转帖或引用,还请注明出处并告知于我。 -### 0.4 推荐编译环境 +## 1.4. 推荐编译环境 C++编译器众多,且对模板的支持可能存在细微差别。如果没有特别强调,本书行文过程中,使用了下列编译器来测试文中提供的代码和示例: @@ -116,9 +98,9 @@ C++编译器众多,且对模板的支持可能存在细微差别。如果没 |---|---| | std::decay_t | C++ 14 | -### 0.5 体例 +## 1.5. 体例 -#### 0.5.1 示例代码 +### 1.5.1. 示例代码 ```C++ void SampleCode() { @@ -126,7 +108,7 @@ void SampleCode() { } ``` -#### 0.5.2 引用 +### 1.5.2. 引用 引用自C++标准: @@ -137,7 +119,7 @@ void SampleCode() { > 《书名》 > 这是一段引用或翻译自其他图书的文字 -### 0.6 意见、建议、喷、补遗、写作计划 +## 1.6. 意见、建议、喷、补遗、写作计划 * 需增加: * 模板的使用动机。 @@ -150,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 { @@ -191,7 +177,7 @@ typedef class { 可以看出,通过模板参数替换类型,可以获得很多形式相同的新类型,有效减少了代码量。这种用法,我们称之为“泛型”(Generic Programming),它最常见的应用,即是STL中的容器模板类。 -#### 1.1.2 模板的使用 +### 2.2.3. 模板的使用 对于C++来说,类型最重要的作用之一就是用它去产生一个变量。例如我们定义了一个动态数组(列表)的模板类`vector`,它对于任意的元素类型都具有push_back和clear的操作,我们便可以如下定义这个类: @@ -251,7 +237,7 @@ ClassB 当然,在实例化过程中,被绑定到模板参数上的类型(即模板实参)需要与模板形参正确匹配。 就如同函数一样,如果没有提供足够并匹配的参数,模板便不能正确的实例化。 -#### 1.1.3 模板类的成员函数定义 +### 2.2.4. 模板类的成员函数定义 由于C++11正式废弃“模板导出”这一特性,因此在模板类的变量在调用成员函数的时候,需要看到完整的成员函数定义。因此现在的模板类中的成员函数,通常都是以内联的方式实现。 例如: @@ -314,9 +300,9 @@ void vector /*看起来像偏特化*/ ::clear() // 函数的实现放在这 } ``` -### 1.2 Template Function的基本语法 +## 2.3. 函数模板 (Function Tempalte) 入门 -#### 1.2.1 Template Function的声明和定义 +### 2.3.1. Function Template 的声明和定义 模板函数的语法与模板类基本相同,也是以关键字`template`和模板参数列表作为声明与定义的开始。模板参数列表中的类型,可以出现在参数、返回值以及函数体中。比方说下面几个例子 @@ -380,7 +366,7 @@ template void foo() 当然和“设计模式”一样,模板在实际应用中,也会有一些固定的需求和解决方案。比较常见的场景包括:泛型(最基本的用法)、通过类型获得相应的信息(型别萃取)、编译期间的计算、类型间的推导和变换(从一个类型变换成另外一个类型,比如boost::function)。这些本文在以后的章节中会陆续介绍。 -#### 1.2.2 模板函数的使用 +### 2.3.2. 模板函数的使用 我们先来看一个简单的函数模板,两个数相加: @@ -527,7 +513,7 @@ int v = 0; float i = c_style_cast(v); // 形象地说,DstT会先把你指定的参数吃掉,剩下的就交给编译器从函数参数列表中推导啦。 ``` -### 1.3 整型也可是Template参数 +## 2.4. 整型也可是Template参数 模板参数除了类型外(包括基本类型、结构、类类型等),也可以是一个整型数(Integral Number)。这里的整型数比较宽泛,包括布尔型,不同位数、有无符号的整型,甚至包括指针。我们将整型的模板参数和类型作为模板参数来做一个对比: @@ -607,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),是一个改变语言内涵,拓展语言外延的存在。 程序最根本的目的是什么?复现真实世界或人所构想的规律,减少重复工作的成本,或通过提升规模完成人所不能及之事。但是世间之事万千,有限的程序如何重现复杂的世界呢? @@ -761,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 @@ -797,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) @@ -868,7 +854,7 @@ Variant result = addFloatOrMulInt(aVar, bVar); 在模板代码中,这个“合适的机制”就是指“特化”和“部分特化(Partial Specialization)”,后者也叫“偏特化”。 -#### 2.2.2 特化 +### 3.2.2. 特化 我的高中物理老师对我说过一句令我受用至今的话:把自己能做的事情做好。编写模板程序也是一样。当你试图用模板解决问题之前,先撇开那些复杂的语法要素,用最直观的方式表达你的需求: @@ -1087,7 +1073,7 @@ void PrintID() 如果你体味到了这一点,那么恭喜你,你的模板元编程已经开悟了。 -#### 2.2.3 特化:一些其它问题 +### 3.2.3. 特化:一些其它问题 在上一节结束之后,你一定做了许多的练习。我们再来做三个练习。第一,给`float`一个ID;第二,给`void*`一个ID;第三,给任意类型的指针一个ID。先来做第一个: @@ -1352,9 +1338,9 @@ void PrintID() 但是呢,直觉对付更加复杂的问题还是没用的(也不是没用,主要是你没这个直觉了)。我们要把这个直觉,转换成合理的规则——即模板的匹配规则。 当然,这个匹配规则是对复杂问题用的,所以我们会到实在一眼看不出来的时候才会动用它。一开始我们只要把握:**模板是从最特殊到最一般形式进行匹配的** 就可以了。 -### 2.3 即用即推导 +## 3.3. 即用即推导 -#### 2.3.1 视若无睹的语法错误 +### 3.3.1. 视若无睹的语法错误 这一节我们将讲述模板一个非常重要的行为特点:那就是什么时候编译器会对模板进行推导,推导到什么程度。 这一知识,对于理解模板的编译期行为、以及修正模板编译错误都非常重要。 @@ -1390,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代码: @@ -1648,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 中提到的四个问题,还有三个没有解决: @@ -1743,7 +1729,7 @@ template struct X { }; ``` -### 2.4 本章小结 +## 3.4. 本章小结 这一章是写作中最艰难的一章,中间停滞了将近一年。因为要说清楚C++模板中一些语法噪音和设计决议并不是一件轻松的事情。不过通过这一章的学习,我们知道了下面这几件事情: @@ -1755,11 +1741,11 @@ template struct X { 从下一章开始,我们将进入元编程环节。我们将使用大量的示例,一方面帮助巩固大家学到的模板知识,一方面也会引导大家使用函数式思维去解决常见的问题。 -## 3 深入理解特化与偏特化 +# 4. 深入理解特化与偏特化 -### 3.1 正确的理解偏特化 +## 4.1. 正确的理解偏特化 -#### 3.1.1 偏特化与函数重载的比较 +### 4.1.1. 偏特化与函数重载的比较 在前面的章节中,我们介绍了偏特化的形式、也介绍了简单的用例。因为偏特化和函数重载存在着形式上的相似性,因此初学者便会借用重载的概念,来理解偏特化的行为。只是,重载和偏特化尽管相似但仍有差异。 @@ -1913,7 +1899,7 @@ X v8; 其他的示例可以先自己推测一下, 再去编译器上尝试一番:[`goo.gl/9UVzje`](http://goo.gl/9UVzje)。 -#### 3.1.2 不定长的模板参数 +### 4.1.2. 不定长的模板参数 不过这个时候也许你还不死心。有没有一种办法能够让例子`DoWork`像重载一样,支持对长度不一的参数列表分别偏特化/特化呢? @@ -2032,7 +2018,7 @@ template class Y {}; // (4) error! 在这里,我们只提到了变长模板参数的声明,如何使用我们将在第四章讲述。 -#### 3.1.3 模板的默认实参 +### 4.1.3. 模板的默认实参 在上一节中,我们介绍了模板对默认实参的支持。当时我们的例子很简单,默认模板实参是一个确定的类型`void`或者自定义的`null_type`: @@ -2049,7 +2035,7 @@ template < 第一步,我们先把浮点正确的写出来: ```C++ -#include +include template T CustomDiv(T lhs, T rhs) { // Custom Div的实现 @@ -2084,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; @@ -2125,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; @@ -2179,7 +2165,7 @@ void foo(){ * A和B都与模板实参无法匹配,所以使用原型,调用`CustomDiv` -### 3.2 后悔药:SFINAE +## 4.2. 后悔药:SFINAE 考虑下面这个函数模板: @@ -2525,9 +2511,9 @@ void doSomething() { ```C++ -#include -#include -#include +include +include +include struct ICounter {}; struct Counter: public ICounter { @@ -2632,17 +2618,18 @@ void foo( 虽然它写起来并不直观,但是对于既没有编译器自省、也没有Concept的C++11来说,已经是最好的选择了。 -### Concept “概念” +## 4.3. Concept “概念”:对模板参数约束的直接描述 -#### “概念” 解决了什么问题 +### 4.3.1. “概念” 解决了什么问题 从上一节可以看出,我们兜兜转转了那么久,就是为了解决两个问题: 1. 在模板进行特化的时候,盘算一下并告诉编译器这里能不能特化; 2. 在函数决议面临多个候选的时候,如果有且仅有其中一个原型能够被函数决议接纳,那就决定是你了! -如果能够直接表达给编译器,不就不用这么麻烦了么。其实在很多现代语言中,都有类似的语言要素存在,比如C#的约束(constraint on type parameters): -``` C# +如果语言能允许用户直接描述需求并传达给编译器,不就不用这么麻烦了么。其实在很多现代语言中,都有类似的语言要素存在,比如C的约束(constraint on type parameters): + +``` C public class Employee { // ... } @@ -2685,15 +2672,15 @@ void inc_counter(T& intTypeCounter) { ``` 直接告诉编译器,我们对T的要求是你得有`++`。 -当然有人会问,那能不能直接写成以下形式,不是更简单吗 +当然有人会问,那能不能直接写成以下形式,不是更简单吗? ``` C++ template requires (T t) { ++t; } void inc_counter(T& cnt); ``` -答案是不能。 -因为`requires`作为keyword是存在二义性的。当它用于模板函数或者模板类的声明时,它是一个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++就是这么的简(~~有~~)单(~~病~~)! @@ -2704,7 +2691,8 @@ void inc_counter(T& cnt); 总而言之,除了这些烦人的问题,“概念”的出现,使得模板的出错提示也清爽了些许 —— 虽然大佬们都在鼓吹concept让模板出错多么好调试,但是实际上模板出错,有一半是来源自类型系统本质上的复杂性,概念并不能解决这一问题。 -比如这里使用SFINAE的提示 +比如这里使用SFINAE的提示: + ``` :23:5: error: no matching function for call to 'Inc' Inc(y); @@ -2729,44 +2717,48 @@ template concept Incrementable = requires(T t) { ++t; }; ``` -虽然看起来要更长一点,但是对于复杂类型来说,还是会友善许多。以后会找个例子给大家陈述。 +虽然在这个例子中,通过 *Concept* 获得出错提示看起来要比使用 *SFINAE* 所获得的错误描述要更长一点,但是对于更加复杂类型来说,则会友善许多。以后会找个例子给大家陈述。 + +### 4.3.2. "概念"入门 -## !!! 以下章节未完成 !!! +# 5. 未完成章节 -## 4 元编程下的数据结构与算法 -### 4.1 表达式与数值计算 -### 4.1 获得类型的属性——类型萃取(Type Traits) -### 4.2 列表与数组 -### 4.3 字典结构 -### 4.4 “快速”排序 -### 4.5 其它常用的“轮子” +``` +# 6. 元编程下的数据结构与算法 +## 6.1. 表达式与数值计算 +## 6.2. 获得类型的属性——类型萃取(Type Traits) +## 6.3. 列表与数组 +## 6.4. 字典结构 +## 6.5. “快速”排序 +## 6.6. 其它常用的“轮子” -## 非模板的编译期计算 +# 7. 非模板的编译期计算 -## 5 模板的进阶技巧 -### 5.1 嵌入类 -### 5.2 Template-Template Class -### 5.3 高阶函数 -### 5.4 闭包:模板的“基于对象” +# 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 From cd4232437e278790abab410b20bb1869cfbd73d0 Mon Sep 17 00:00:00 2001 From: Ye Wu Date: Tue, 11 Oct 2022 19:19:00 -0700 Subject: [PATCH 97/99] =?UTF-8?q?Reclarified=20=E6=9C=AF=E8=AF=AD=20?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E7=B1=BB=20=E5=92=8C=20=E7=B1=BB=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=20=E7=9A=84=E6=AD=A3=E7=A1=AE=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReadMe.md | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index c6c0cd1..f311517 100644 --- a/ReadMe.md +++ b/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`来实例化模板类ClassA,那么`ClassA`可以等同于以下的定义: +例如我们用`ClassA`来实例化类模板ClassA,那么`ClassA`可以等同于以下的定义: ``` C++ // 注意:这并不是有效的C++语法,只是为了说明模板的作用 @@ -175,11 +175,11 @@ typedef class { } ClassA; ``` -可以看出,通过模板参数替换类型,可以获得很多形式相同的新类型,有效减少了代码量。这种用法,我们称之为“泛型”(Generic Programming),它最常见的应用,即是STL中的容器模板类。 +可以看出,通过模板参数替换类型,可以获得很多形式相同的新类型,有效减少了代码量。这种用法,我们称之为“泛型”(Generic Programming),它最常见的应用,即是STL中的容器类模板。 ### 2.2.3. 模板的使用 -对于C++来说,类型最重要的作用之一就是用它去产生一个变量。例如我们定义了一个动态数组(列表)的模板类`vector`,它对于任意的元素类型都具有push_back和clear的操作,我们便可以如下定义这个类: +对于C++来说,类型最重要的作用之一就是用它去产生一个变量。例如我们定义了一个动态数组(列表)的类模板`vector`,它对于任意的元素类型都具有push_back和clear的操作,我们便可以如下定义这个类: ```C++ template @@ -208,14 +208,14 @@ 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,...] > @@ -237,9 +237,9 @@ ClassB 当然,在实例化过程中,被绑定到模板参数上的类型(即模板实参)需要与模板形参正确匹配。 就如同函数一样,如果没有提供足够并匹配的参数,模板便不能正确的实例化。 -### 2.2.4. 模板类的成员函数定义 +### 2.2.4. 类模板的成员函数定义 -由于C++11正式废弃“模板导出”这一特性,因此在模板类的变量在调用成员函数的时候,需要看到完整的成员函数定义。因此现在的模板类中的成员函数,通常都是以内联的方式实现。 +由于C++11正式废弃“模板导出”这一特性,因此在类模板的变量在调用成员函数的时候,需要看到完整的成员函数定义。因此现在的类模板中的成员函数,通常都是以内联的方式实现。 例如: ``` C++ @@ -302,9 +302,9 @@ void vector /*看起来像偏特化*/ ::clear() // 函数的实现放在这 ## 2.3. 函数模板 (Function Tempalte) 入门 -### 2.3.1. Function Template 的声明和定义 +### 2.3.1. 函数模板的声明和定义 -模板函数的语法与模板类基本相同,也是以关键字`template`和模板参数列表作为声明与定义的开始。模板参数列表中的类型,可以出现在参数、返回值以及函数体中。比方说下面几个例子 +函数模板的语法与类模板基本相同,也是以关键字`template`和模板参数列表作为声明与定义的开始。模板参数列表中的类型,可以出现在参数、返回值以及函数体中。比方说下面几个例子 ```C++ template void foo(T const& v); @@ -342,7 +342,7 @@ template void foo() 举个例子:generic typed function ‘add’ ``` -在我遇到的朋友中,即便如此对他解释了模板,即便他了解了模板,也仍然会对模板产生畏难情绪。毕竟从形式上来说,模板类和模板函数都要较非模板的版本更加复杂,阅读代码所需要理解的内容也有所增多。 +在我遇到的朋友中,即便如此对他解释了模板,即便他了解了模板,也仍然会对模板产生畏难情绪。毕竟从形式上来说,模板化的类和模板化的函数都要较非模板的版本更加复杂,阅读代码所需要理解的内容也有所增多。 如何才能克服这一问题,最终视模板如平坦代码呢? @@ -356,7 +356,7 @@ template void foo() 3. 把解决方案用代码写出来。 - 4. 如果失败了,找到原因。是知识有盲点(例如不知道怎么将 `T&` 转化成 `T`),还是不可行(比如试图利用浮点常量特化模板类,但实际上这样做是不可行的)? + 4. 如果失败了,找到原因。是知识有盲点(例如不知道怎么将 `T&` 转化成 `T`),还是不可行(比如试图利用浮点常量特化类模板,但实际上这样做是不可行的)? 通过重复以上的练习,应该可以对模板的语法和含义都有所掌握。如果提出问题本身有困难,或许下面这个经典案例可以作为你思考的开始: @@ -366,7 +366,7 @@ template void foo() 当然和“设计模式”一样,模板在实际应用中,也会有一些固定的需求和解决方案。比较常见的场景包括:泛型(最基本的用法)、通过类型获得相应的信息(型别萃取)、编译期间的计算、类型间的推导和变换(从一个类型变换成另外一个类型,比如boost::function)。这些本文在以后的章节中会陆续介绍。 -### 2.3.2. 模板函数的使用 +### 2.3.2. 函数模板的使用 我们先来看一个简单的函数模板,两个数相加: @@ -465,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); @@ -1151,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这个成员。 } ``` @@ -1491,7 +1491,7 @@ template 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++就是这么的简(~~有~~)单(~~病~~)! From 003d34cc6fb46a447cb8cda764ddbb6f86463144 Mon Sep 17 00:00:00 2001 From: c_cpp_a <111665869+c-cpp-a@users.noreply.github.com> Date: Tue, 18 Apr 2023 18:29:45 +0800 Subject: [PATCH 98/99] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原因:在实际的类模板中,可能会有默认模板参数。比如: template class vector{ //... }; 这样就会允许vector<>的出现(里面是没有模板实参的!)。 --- ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index f311517..dd5671b 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -218,7 +218,7 @@ vector unknownVector; // 错误示例 这样就是错误的。我们把通过类型绑定将类模板变成“普通的类”的过程,称之为模板实例化(Template Instantiate)。实例化的语法是: ``` -模板名 < 模板实参1 [,模板实参2,...] > +模板名 < [模板实参1,模板实参2,...] > ``` 看几个例子: From bd897729f5dba42608cdf2823d11d2554873c4cc Mon Sep 17 00:00:00 2001 From: Yangtze Date: Tue, 20 Feb 2024 02:30:33 -0500 Subject: [PATCH 99/99] Update ReadMe.md (#74) --- ReadMe.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index dd5671b..8a85d9b 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -18,7 +18,7 @@ - [2.2.2. Class Template的与成员变量定义](#222-class-template的与成员变量定义) - [2.2.3. 模板的使用](#223-模板的使用) - [2.2.4. 类模板的成员函数定义](#224-类模板的成员函数定义) - - [2.3. 函数模板 (Function Tempalte) 入门](#23-函数模板-function-tempalte-入门) + - [2.3. 函数模板 (Function Template) 入门](#23-函数模板-function-template-入门) - [2.3.1. 函数模板的声明和定义](#231-函数模板的声明和定义) - [2.3.2. 函数模板的使用](#232-函数模板的使用) - [2.4. 整型也可是Template参数](#24-整型也可是template参数) @@ -300,7 +300,7 @@ void vector /*看起来像偏特化*/ ::clear() // 函数的实现放在这 } ``` -## 2.3. 函数模板 (Function Tempalte) 入门 +## 2.3. 函数模板 (Function Template) 入门 ### 2.3.1. 函数模板的声明和定义