您的位置:首页 > 编程语言 > C语言/C++

C++模板中的名称

2011-03-28 22:39 253 查看
名称的分类:
标识符、运算符id、类型转换函数id、模板id、非受限id、受限id、受限名称、非受限名称、依赖性名称、非依赖性名称
如果一个名称使用域解析运算符或者成员访问运算符来显式表明它所属的作用域,就称该名称为受限名称。
如果一个名称依赖于模板参数,就称为依赖性名称。

名称查找:
受限名称的名称查找是在一个受限作用域内部进行的,该受限作用域由一个限定的构造所决定,如果该作用域是一个类,那么查找范围可以到达它的基类;但不会考虑它的外围作用域。
非受限名称的查找则相反,可以在所有外围类中逐层地进行查找(但在某个类内部定义的成员函数定义中,它会先查找该类和基类的作用域,然后才查找外围类的作用域)

ADL(argument-dependent lookup)依赖于参数的查找:
ADL只能应用于非受限名称。
唯一例外情况是:忽略using指示符

#include <iostream>
namespace X {
template <typename T> void f(T);
}
namespace N {
using namespace X;
enum E { e1 };
void f(E) {
std::cout << "N::f(N::E) called/n";
}
}
void f(int) {
std::cout << "::f(int) called/n";
}
int main() {
::f(N::e1);
f(N::e1);	//执行了ADL,忽略using namespace X
}


友元名称插入:
template <typename T>
class C {
friend void f();
friend void f(C<T> const&);
...
};
void g(C<int>* p) {
f();    //无参数,不能利用ADL,无效调用
f(*p);    //只要在调用之前完全实例化了类C<int>,就可以找到第个友元函数声明
}


C++标准规定:
通常,友元声明在外围(类)作用于中是不可见的。
如果友元函数所在的类属于ADL的关联类集合,那么在这个外围类是可以找到该友元声明的。
对于涉及在关联类中友元查找的调用,会导致该关联类被实例化,如f(*p)。

解析模板:
依赖性类型名称:

依赖型受限名称并不会代表一个类型,除非在该名称的前面有关键字typename前缀。
当类型名称具有以下性质时,就应该在该名称前面添加typename前缀:
1.名称出现在一个模板中
2.名称是受限的
3.名称不是用于指定基类继承的列表中,也不是位于引入构造函数的成员初始化列表中
4.名称依赖于模板参数
只有当前面3个条件同时满足的情况下,才能使用typename前缀。
template <typename T>
struct S : typename X<T>::Base {	//不满足规则3
S() : typename X<T>::Base(typename X<T>::Base(0) ) { }	//第一个typename不满足规则3
typename X<T> f() {		//不满足2
typename X<T>::C *p;	//指针p的声明
X<T>::D* q;		//乘积
}
typename X<int>::C * s;		//可选,不满足规则4
};
struct U {
typename X<int>::C * pc;	//不满足规则1
};


依赖性模板名称

和依赖性类型名称一样,要让编译器知道所引用的依赖型名称是一个模板,需要在该名称前加template关键字,否则编译器不把它作为一个模板名称。

template <typename T>
class Shell
{
public:
template <int N>
class In
{
public:
template <int M>
class Deep
{
public:
virtual void t();
};
};
};

template <typename T,int N>
class Weird
{
public:
void case1(typename Shell<T>::template In<N>::template Deep<N>* p)
{
p->template Deep<N>::t();//禁止虚函数调用
}
void case2(typename Shell<T>::template In<N>::template Deep<N>& p)
{
p.template Deep<N>::t();//禁止虚函数调用

}


这个例子给出了何时需要在运算符(::,->和.,用于限定一个名称)的后面加上template,更精确的说法是:如果限定符号前面的名称的类型依赖于某个模板参数,并且紧接着在限定符后面的是一个template-id(也就是指一个后面带有尖括号内部实参列表的模板名称)那么就应该使用关键字template,例如下面的表达式中:

p.template Deep<N>::t();

p的类型依赖于模板参数T。然而C++编译器并不会查找Deep来判断是否是一个模板,因此我们必须显示指定Deep是一个模板名称。

派生和类模板:

非依赖型基类:无需知道模板实参就可以确定类型的基类。就是说,基类名称是用非依赖性名称表示的。例如:

template <typename X>
class Base {
public:
int basefield;
typename int T;
};
class D1 : public Base<Base<void> > {
public:
void f() { basefield = 3; }
};
template <typename T>
class D2 : public Base<double> {
public:
void f() { basefield = 7; }
T strange;	//此处先查找非依赖型基类再查找模板参数列表,此处T一直都会是Base<double>::T 中对应的T类型(int),而不会是模板参数列表中的T
};


模板中的非依赖性基类和普通非模板类中的基类的性质很相似,但有一点区别:对于模板中非依赖性基类而言,如果在派生类里查找一个非受限名称,那就会先查找这个非依赖性基类,然后在查找模板参数列表,如上述例子。

依赖性基类:

对于模板中的非依赖型名称,将会在看到的第一时间进行查找,但不会在依赖型基类中进行查找。
在允许使用this->前缀的地方都使用this->前缀。
如果不断重复的限定会让代码不雅观,可以在派生类中只引入依赖型基类中的名称一次:
template <typename T>
class D3 : public Base<T> {
public:
using Base<T>::basefield;
void f() { this->basefield = 0; }//用this把basefield依赖性名称,也可以这样Base<T>::basefield = 0,但这样会禁止虚函数调用。
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: