mirror of
https://github.com/wuye9036/CppTemplateTutorial.git
synced 2024-03-22 13:11:16 +08:00
Update document format.
This commit is contained in:
parent
e02f4f5c0a
commit
0fff42ce4b
34
ReadMe.md
34
ReadMe.md
|
@ -1335,19 +1335,21 @@ template <typename T> 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<T> b;
|
||||
}
|
||||
```
|
||||
在这段简短的代码中,就包含了两个歧义的可能,一是`A`是模板,于是`A<T>`是一个实例化的类型,`b`是变量,另外一种就是关系表达式,`((A < T) > b)`。甚至词法分析也会受到语义的干扰,C++11中才明确被修正的`vector<vector<int>>`,就因为`>>`被误解为右移或流操作符,而导致某些编译器上的错误。因此,在语义没有确定之前,连语法都没有分析的价值。
|
||||
在这段简短的代码中,就包含了两个歧义的可能,一是`A`是模板,于是`A<T>`是一个实例化的类型,`b`是变量,另外一种就是关系表达式,`((A < T) > b)`。
|
||||
|
||||
甚至词法分析也会受到语义的干扰,C++11中才明确被修正的`vector<vector<int>>`,就因为`>>`被误解为右移或流操作符,而导致某些编译器上的错误。因此,在语义没有确定之前,连语法都没有分析的价值。
|
||||
|
||||
大约是基于如此考量,为了偷懒,MSVC将包括所有的语法/语义分析工作都挪到了第二个Phase,于是乎连带着语法分析都送进了第二个阶段。符合标准么?显然不符合。
|
||||
|
||||
但是这里值得一提的是,MSVC的做法和标准相比,虽然投机取巧,但并非有弊无利。我们来先说一说坏处。考虑以下例子:
|
||||
```C++
|
||||
// ----------- X.h ------------
|
||||
|
@ -1403,6 +1409,7 @@ X<float> xf;
|
|||
// ... 一些代码 ...
|
||||
```
|
||||
此时如果X中有一些与模板参数无关的错误,如果名称查找/语义分析在两个阶段完成,那么这些错误会很早、且唯一的被提示出来;但是如果一切都在实例化时处理,那么可能会导致不同的实例化过程提示同样的错误。而模板在运用过程中,往往会产生很多实例,此时便会大量报告同样的错误。
|
||||
|
||||
当然,MSVC并不会真的这么做。根据推测,最终他们是合并了相同的错误。因为即便对于模板参数相关的编译错误,也只能看到最后一次实例化的错误信息:
|
||||
```
|
||||
template <typename T> struct X {};
|
||||
|
@ -1459,6 +1466,7 @@ error: no type named 'MemberType' in 'X<float>'
|
|||
4 errors generated.
|
||||
```
|
||||
可以看到,Clang的提示和标准更加契合。它很好地区分了模板在定义和实例化时分别产生的错误。
|
||||
|
||||
另一个缺点也与之类似。因为没有足够的检查,如果你写的模板没有被实例化,那么很可能缺陷会一直存在于代码之中。特别是模板代码多在头文件。虽然不如接口那么重要,但也是属于被公开的部分,别人很可能会踩到坑上。缺陷一旦传播开修复起来就没那么容易了。
|
||||
|
||||
但是正如我前面所述,这个违背了标准的特性,并不是一无是处。首先,它可以完美的兼容标准。符合标准的、能够被正确编译的代码,一定能够被MSVC的方案所兼容。其次,它带来了一个非常有趣的特性,看下面这个例子:
|
||||
|
@ -1515,8 +1523,10 @@ void main() {
|
|||
```
|
||||
|
||||
但是其实我们知道,`foo`要到实例化之后,才需要真正的做语义分析。在MSVC上,因为函数实现就是到模板实例化时才处理的,所以这个例子是完全正常工作的。
|
||||
|
||||
在实际应用中,我们经常既希望把模板类成员函数的声明和实现放到一起,因为模板函数看不到实现也很难调用;又希望一般类型可以声明定义分离,把类型定义隐藏到源文件中,以完成声明实现分离。
|
||||
这个时候,对于符合标准的编译器,我们只能将模板头文件拆分成`<X.h>`和`<X.impl.h>`两个部分,并按照顺序引用两个文件。但是在MSVC中就可以直接将模板函数的实现,和一般类型的声明放在一起,反而更加简单清晰。
|
||||
|
||||
此时如果编译器是符合标准的,我们只能将模板头文件拆分成`<X.h>`和`<X.impl.h>`两个部分,并按照顺序引用两个文件。但是在MSVC中就可以直接将模板函数的实现,和一般类型的声明放在一起,反而更加简单清晰。
|
||||
|
||||
扩展阅读: [The Dreaded Two-Phase Name Lookup][2]
|
||||
###2.4 函数模板的重载、参数匹配、特化与部分特化
|
||||
|
|
Loading…
Reference in New Issue
Block a user