0.简介
大多数编程语言编译器会进行两项基本任务:
1)tokenization,又称为扫描scanning
2)parsing分析,上面的tokenization步骤会把源码读入的一个字符序列characters sequence,
再由parsing,也就是词法分析器,在词汇单元序列中寻找已知的模式patterns。
1. NonTemplate的上下文敏感性(context Sensitivity)
tokenizing要比Parsing容易,幸运的是parsing理论基础已经相当稳固,很多语言都可以根据这个理论不困难地进行parsing。这个理论对于上下文无关地语言最有效,然而C++是一个上下文敏感的语言。为了进行Parsing, C++编译器把一个符合表(symbol table)结合于tokenizer和parser身上:当某个声明被成功解析后,便进入符合表中。当tokenizer找到一个标识符identifier,便查询符号表,如果在其中找到对应(同名)类型,就将resulting token标注出来。
举个例子,如果C++编译器看到
x*
于是tokenizer查询x。如果在符合表中找到类型x,parser会看到
identifier, type,
x symbol, *
于是推断位一个声明的开始,然而如果x不是类型,那么parser会从tokenizer获得:
identifier, nontype,
x symbol, *
于是这个构件construct就只能被合法解析为一个乘法操作。这些原则的细节取决于具体操作策略,但要旨没变。
下面例子说明上下文敏感性context sensitivity。考虑以下表达式:
x<1>(0)
如果x是个class template名称,这个表达式便是将整数0转型为X<1>所指涉的类型。但如果X不是个template。这个表达式等价于:
(X<1 ) > 0
换句话说X和1的比较结果(true或false)被隐式转型为1或0,然后再与0比较。虽然这种写法很少见,但它确实是合法的C++程序代码。只有C++ parser看到一个template名称时,它才确实是合法C++代码。只有当 C++ parser看到一个template名称时,它才认为其后面跟的<是角括号,否则就当作小于号。
这种上下文敏感,是当初以角括号作为模板参数列表界定符合的不幸产物。下面是另一个例子:
template <bool B>
class Invert
{
public:
static bool const result = !B;
};
void g()
{
bool test = Invert<(1>0)>::result;
}
如果圆括号不存在,就会被编译器误认为非法算式。
2. 类型的受控名称Dependent Names
Templates内的名称,其问题在于:它们往往不具备足够的确定性。更明确地说,一个template不能窥见另一个模板template的内部,因为后者的内容可能因为明确特化(explicit specialization)而变得不合法。下面的例子可以说明这一点:
template <typename T>
class Trap{
public:
enum {x}; // (1) x is not a type
};
template <typename T>
class Victim
{
public:
int y;
void proof()
{
Trap<T>::x * y; // (2) 这是一个声明还是一个乘法算法?
}
};
template <>
class Trap<void> //恶意特化
{
public:
typedef int x; //(3) x is one type
};
void boom(Victim<void>& bomb)
{
bomb.proof();
}
编译器对(2)进行parsing时,它必须确定是个声明还是乘法,而这取决于受控受饰名称dependent qualified name Trap<T>::x是不是一个类型名称。但是这有个问题:
从(1)看Trap<T>::x不是类型,(2)是个乘法;
但从(3)来看Trap<T>::x 事实上成了int 类型。
C++对此问题做了明确规定:通常一个受控受饰名称dependent qualified name并不指涉某个类型,除非该名称以关键词typename为前导。
3. 模板的受控名称Dependent Names
4 Using声明语句中的受控名称Dependent Names
5. ADL和 显式模板实参Explicit Template Argument