模板中的名称
一、名称的分类
(1)受限名称:名称前面出现 ::, ->, .的名称,比如::X, S::x, this->x, ::N::A<int>::s
(2)非受限名称:除 受限名称 外
(3)依赖型名称:一个以某种方法依赖于模板参数的名称。
①显示包含模板参数的 受限名称 和 非受限名称 都是依赖型名称,
比如::N::A<T>::s //受限, S<int> //非受限
②对于一个成员访问运算符(., ->)限定的受限名称,如果符号左边的表达式类型依赖于模板参数,该受限名称也是依赖型
比如 void fun(C<T>* p){ p->x; } //因为p是一个依赖型的名称,即 x 也是一个依赖型的名称
③对于this->b; 同②。如果是在模板出现这句话那么b也是依赖型名称。
template<typename T>
class C{
int b;
void fun(){
this->b = 1; //b为依赖型名称
//但如果是 b=1; 则该表达式中b不是受限名称,也不是依赖型名称
}
};
④对于形如ident(x,y,z)的调用,如果其中有某个参数或者是表达式所属的类型是一个依赖于模板参数的类型,那么ident
也是一个依赖型名称。
(4)非依赖型名称:除 依赖型名称 外
那么现在就有四种组合
|
受限
|
非受限 |
依赖型
|
受限依赖型
|
非受限依赖型
|
非依赖型
|
受限非依赖型
|
非受限非依赖型
|
二、名称查找
(1)普通查找:受限名称 查找是在一个受限的作用域内进行的。如果该作用域是一个类,那么查找范围可以到达他的基类,
但不会考虑外围作用域。比如D::a只会在类D及其基类中查找
非受限名称 查找是由内到外的在所有外围类中组曾地进行查找,如果是在类内部定义的成员函数的定义中,他会先查找类内部和该基类内部作用域,再查找外围类的作用域。
(2)ADL查找:首先查找对象应该是 非受限名称 。这个前提很重要。
根据函数参数的类型查找关联类 和 关联名字空间。然后在这个查找集合中选出最佳的函数声明。
template<typename T>
intline T max(T const& a, T const& b)
{
return a > b ? a : b; //这里需要使用到特殊的operator > 实现,但是max不知道BigMath名字空间的存在
}
namespace BigMath{
class BigNum{};
bool operatoe > (BigNum const&, BigNum const&);
}
using BigMath::BigNum;
void g(BigNum const& a, BigNum const& b)
{
BigNum c = max(a, b);
}
所以上面的查找需要ADL查找,才能够根据参数a, b的类型找到operator > ()的具体实现。
三、友元问题
C++ 规定: 友元的声明在外围类作用域中是不可见的
C++还规定:如果友元函数所在的类属于ADL的关联类集合,那么我们在这个外围类是可以找到该友元声明的
四、插入式类模板名称
首先,插入式类名称应该是该类作用域的非受限名称(如果是该类作用域的受限名称,则会变成该类的构造函数,称该名称将访问不到)。
模板的插入式类名称,与普通的插入式类名称有些区别:
类模板名称后面可以紧跟模板实参,如果没有紧跟模板实参那么他们就是用模板参数来代表实参的类
template< template<typename> class TT> class X {};
template<typename T> class C{
C* a;
C<void> b;
X<C> c; //没有跟模板实参,C将不会被看做是模板
X<::C> d; //<::之间没有空格,<:是[ 的另一种表示
X< ::C> e; //正确
}
五、解析模板
大多数程序设计语言编译都包含两个我最基本的步骤:符号标记(词法扫描)和解析。
(一)依赖型类型名称
下面是一个很经典的例子,
①先是定义一个类模板A,然后在另一个类模板B中引用该模板A中的一个名称x,
②然后显示特化类模板A变成类型C,其中改变了名称x的含义
③调用实例化后的类模板B的一个成员函数。
template<typename T>
class Trap [
public:
enum{ x }; //(1)这里的x不是一个类型
];
template<typename T>
class Victim{
public:
int y;
void poof(){
Trap<T>::x*y; //这里x*y究竟是乘积还是声明一个指针y?
}
};
template<>
class Trap<void>{
public:
typedef int x; //这里改变了x的含义
};
void boom(Victim<void>& boom)
{
boom.poof();
}
解决办法:
C++规定:通常而言,依赖型受限名称并不会代表一个类型,除非在该名称的前面加上关键字typename
当类型出现以下前3个性质时,就应该在该名称前面添加typename。
(1)名称出现在类模板中
(2)名称是受限名称
(3)名称不是用于指定基类继承列表中,也不是位于引入构造函数的成员初始化列表中。
(4)名称依赖于模板参数
(二)依赖型模板名称
如果模板名称是依赖型名称,那么位于限定符后面的模板名称与<后面的模板实参应该如何解析,就需要在
受限的依赖型模板名称前面加上关键字template
(三)using 与 private 继承
(1)受限的类名
template<typename T>
class BXT{
public:
typedef T Mystery;
template<typenaeme U>
struct Magic;
};
template<typename T>
class DXT : private BXT<T>{
public:
using typename BXT<T>::Mystery;
Mystery* p;
};
(2)受限的模板名(这个是错误的,书上是这么说的,但是我在VS2013是可以的)
template<typename T>
class DXTM : private BXT<T>{
public:
using BXT<T>::template Magic;
Magic<T>* plink;
};
(四)派生类与类模板
(1)非依赖型基类
对于模板中的非依赖型基类, 如果在它的派生类中查找一个非受限名称,那就会先查找这个非依赖型基类然后查找模板参数列表。
(2)依赖型基类
C++规定:非依赖型名称将会在看到第一时间进行查找
C++声明:非依赖型名称不会在依赖型基类中进行查找
(这个给我们创造了延迟查找的机会,即将名称变为依赖型,因为依赖型名称也要在实例化时才进行查找)
template<typename X>
class Base{
public:
int basefield; //非受限名称
typename int T;
};
template <typename T>
class DD :public Base<T>{
public:
void f(){ basefield = 0; }; //①非依赖型的名称一开始就查找到了,并绑定了int
};
template<>
class Base<bool>{
public:
enum{ basefield = 42 }; //②特化的时候改变了原来的含义
};
int _tmain(int argc, _TCHAR* argv[])
{
DD<bool> d;
d.f();
return 0;
}
所以可以借助依赖型延迟查找的特性,将basefield变成依赖型,即将①变成: this->basefield = 0;
利用依赖型查找的延迟特性,也可以启用类模板的多态
template<typename T>
class B{
public:
enum E{ e1, e2, e3};
virtual void zero(E e = e1);
virtual void one(E&);
};
template<typename T>
class D : public B<T>{
public:
void f(){
typename D<T>::E e;
this->zero(); // D<T>::zero()会禁止虚函数调用
this->one(e); //one()的参数就是依赖型,不过最好还是使用this指针
}
};
但是上面的一切都在VS2013都解决了,比如可以不让basefield 成为依赖型仍然可以正确的实例化。
可能编译器厂商已经支持了,不过标准还是遵守吧。