Update document format.

This commit is contained in:
Ye WU 2015-11-24 21:19:23 -08:00
parent e02f4f5c0a
commit 0fff42ce4b

View File

@ -1335,19 +1335,21 @@ template <typename T> foo(T& v0, C& v1){
因此在模板定义的地方进行语义分析,并不能**完全**得出代码是正确或者错误的结论只有到了实例化阶段确定了模版参数的类型后才知道这段代码正确与否。令人高兴的是在这一问题上我们和C++标准委员会的见地一致说明我们的C++水平已经和Herb Sutter不分伯仲了。既然我们和Herb Sutter水平差不多那凭什么人家就吃香喝辣下面我们来选几条标准看看服不服 因此在模板定义的地方进行语义分析,并不能**完全**得出代码是正确或者错误的结论只有到了实例化阶段确定了模版参数的类型后才知道这段代码正确与否。令人高兴的是在这一问题上我们和C++标准委员会的见地一致说明我们的C++水平已经和Herb Sutter不分伯仲了。既然我们和Herb Sutter水平差不多那凭什么人家就吃香喝辣下面我们来选几条标准看看服不服
> ###14.6 名称解析Name resolution > ###14.6 名称解析Name resolution
**1)** 模板定义中能够出现以下三类名称:
—— 模板名称、或模板实现中所定义的名称; > **1)** 模板定义中能够出现以下三类名称:
—— 和模板参数有关的名称;
—— 模板定义所在的定义域内能看到的名称。 > —— 模板名称、或模板实现中所定义的名称;
> —— 和模板参数有关的名称;
**9)** … 如果名字查找和模板参数有关,那么查找会延期到模板参数全都确定的时候。 … > —— 模板定义所在的定义域内能看到的名称。
**10)** 如果(模板定义内出现的)名字和模板参数无关,那么在模板定义处,就应该找得到这个名字的声明。… > …
> **9)** … 如果名字查找和模板参数有关,那么查找会延期到模板参数全都确定的时候。 …
> **10)** 如果(模板定义内出现的)名字和模板参数无关,那么在模板定义处,就应该找得到这个名字的声明。…
> ###14.6.2 依赖性名称Dependent names > ###14.6.2 依赖性名称Dependent names
**1)** …(模板定义中的)表达式和类型可能会依赖于模板参数,并且模板参数会影响到名称查找的作用域 … 如果表达式中有操作数依赖于模板参数,那么整个表达式都依赖于模板参数,名称查找延期到**模板实例化时**进行。并且定义时和实例化时的上下文都会参与名称查找。(依赖性)表达式可以分为类型依赖(类型指模板参数的类型)或值依赖。 > **1)** …(模板定义中的)表达式和类型可能会依赖于模板参数,并且模板参数会影响到名称查找的作用域 … 如果表达式中有操作数依赖于模板参数,那么整个表达式都依赖于模板参数,名称查找延期到**模板实例化时**进行。并且定义时和实例化时的上下文都会参与名称查找。(依赖性)表达式可以分为类型依赖(类型指模板参数的类型)或值依赖。
> ####14.6.2.2 **类型依赖的表达式** > ####14.6.2.2 **类型依赖的表达式**
**2)** 如果成员函数所属的类型是和模板参数有关的,那么这个成员函数中的`this`就认为是类型依赖的。 > **2)** 如果成员函数所属的类型是和模板参数有关的,那么这个成员函数中的`this`就认为是类型依赖的。
> ###14.6.3 非依赖性名称Non-dependent names > ###14.6.3 非依赖性名称Non-dependent names
**1)** 非依赖性名称在**模板定义**时使用通常的名称查找规则进行名称查找。 > **1)** 非依赖性名称在**模板定义**时使用通常的名称查找规则进行名称查找。
[Working Draft: Standard of Programming Language C++, N3337][1] [Working Draft: Standard of Programming Language C++, N3337][1]
@ -1384,8 +1386,12 @@ void foo(){
A<T> b; 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将包括所有的语法/语义分析工作都挪到了第二个Phase于是乎连带着语法分析都送进了第二个阶段。符合标准么显然不符合。
但是这里值得一提的是MSVC的做法和标准相比虽然投机取巧但并非有弊无利。我们来先说一说坏处。考虑以下例子 但是这里值得一提的是MSVC的做法和标准相比虽然投机取巧但并非有弊无利。我们来先说一说坏处。考虑以下例子
```C++ ```C++
// ----------- X.h ------------ // ----------- X.h ------------
@ -1403,6 +1409,7 @@ X<float> xf;
// ... 一些代码 ... // ... 一些代码 ...
``` ```
此时如果X中有一些与模板参数无关的错误如果名称查找/语义分析在两个阶段完成,那么这些错误会很早、且唯一的被提示出来;但是如果一切都在实例化时处理,那么可能会导致不同的实例化过程提示同样的错误。而模板在运用过程中,往往会产生很多实例,此时便会大量报告同样的错误。 此时如果X中有一些与模板参数无关的错误如果名称查找/语义分析在两个阶段完成,那么这些错误会很早、且唯一的被提示出来;但是如果一切都在实例化时处理,那么可能会导致不同的实例化过程提示同样的错误。而模板在运用过程中,往往会产生很多实例,此时便会大量报告同样的错误。
当然MSVC并不会真的这么做。根据推测最终他们是合并了相同的错误。因为即便对于模板参数相关的编译错误也只能看到最后一次实例化的错误信息 当然MSVC并不会真的这么做。根据推测最终他们是合并了相同的错误。因为即便对于模板参数相关的编译错误也只能看到最后一次实例化的错误信息
``` ```
template <typename T> struct X {}; template <typename T> struct X {};
@ -1459,6 +1466,7 @@ error: no type named 'MemberType' in 'X<float>'
4 errors generated. 4 errors generated.
``` ```
可以看到Clang的提示和标准更加契合。它很好地区分了模板在定义和实例化时分别产生的错误。 可以看到Clang的提示和标准更加契合。它很好地区分了模板在定义和实例化时分别产生的错误。
另一个缺点也与之类似。因为没有足够的检查,如果你写的模板没有被实例化,那么很可能缺陷会一直存在于代码之中。特别是模板代码多在头文件。虽然不如接口那么重要,但也是属于被公开的部分,别人很可能会踩到坑上。缺陷一旦传播开修复起来就没那么容易了。 另一个缺点也与之类似。因为没有足够的检查,如果你写的模板没有被实例化,那么很可能缺陷会一直存在于代码之中。特别是模板代码多在头文件。虽然不如接口那么重要,但也是属于被公开的部分,别人很可能会踩到坑上。缺陷一旦传播开修复起来就没那么容易了。
但是正如我前面所述这个违背了标准的特性并不是一无是处。首先它可以完美的兼容标准。符合标准的、能够被正确编译的代码一定能够被MSVC的方案所兼容。其次它带来了一个非常有趣的特性看下面这个例子 但是正如我前面所述这个违背了标准的特性并不是一无是处。首先它可以完美的兼容标准。符合标准的、能够被正确编译的代码一定能够被MSVC的方案所兼容。其次它带来了一个非常有趣的特性看下面这个例子
@ -1515,8 +1523,10 @@ void main() {
``` ```
但是其实我们知道,`foo`要到实例化之后才需要真正的做语义分析。在MSVC上因为函数实现就是到模板实例化时才处理的所以这个例子是完全正常工作的。 但是其实我们知道,`foo`要到实例化之后才需要真正的做语义分析。在MSVC上因为函数实现就是到模板实例化时才处理的所以这个例子是完全正常工作的。
在实际应用中,我们经常既希望把模板类成员函数的声明和实现放到一起,因为模板函数看不到实现也很难调用;又希望一般类型可以声明定义分离,把类型定义隐藏到源文件中,以完成声明实现分离。 在实际应用中,我们经常既希望把模板类成员函数的声明和实现放到一起,因为模板函数看不到实现也很难调用;又希望一般类型可以声明定义分离,把类型定义隐藏到源文件中,以完成声明实现分离。
这个时候,对于符合标准的编译器,我们只能将模板头文件拆分成`<X.h>`和`<X.impl.h>`两个部分并按照顺序引用两个文件。但是在MSVC中就可以直接将模板函数的实现和一般类型的声明放在一起反而更加简单清晰。
此时如果编译器是符合标准的,我们只能将模板头文件拆分成`<X.h>`和`<X.impl.h>`两个部分并按照顺序引用两个文件。但是在MSVC中就可以直接将模板函数的实现和一般类型的声明放在一起反而更加简单清晰。
扩展阅读: [The Dreaded Two-Phase Name Lookup][2] 扩展阅读: [The Dreaded Two-Phase Name Lookup][2]
###2.4 函数模板的重载、参数匹配、特化与部分特化 ###2.4 函数模板的重载、参数匹配、特化与部分特化