C++ Templates:模板中的名称

名称的分类:
标识符、运算符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)。

插入式类名称:
如果在类本身的作用于中插入该类的名称,就称该名称为插入式类名称。

#include <iostream>

int C;

class C {
private:
    int i[2];
public:
    static int f() {
        return sizeof(C);
    }
};

int f() {
    return sizeof(C);
}

int main() {
    std::cout << "C::f() = " << C::f() << ", "    //返回类C的大小
        << " ::f() = " << ::f() << std::endl;    //返回变量C的大小
}
在类模板中使用插入式名称,和普通插入式类名称有些区别:它们的后面可以紧跟模板实参
template <template<typename T> class TT> class X {
};

template <typename T> class C {
    C* a;        //正确,等价于C<T>* a
    C<void> b;    //正确
    X<C> c;        //错误,后面没有模板实参列表的C不被看作模板
    X<::C> d;    //错误,<: 是[ 的另一种标记
    X< ::C> e;    //正确,在< 和:: 之间的空格是需要的
};

解析模板:
maximum munch扫描原则:C++实现应该让一个标记具有尽可能多的字符。
通常,依赖型受限名称并不会代表一个类型,除非在该名称的前面有关键字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
};
上面红色标注的typename是不必使用的。

如果限定符号前面的名称(或者表达式)的类型要依赖于某个模板参数,并且紧接在限定符后面的是一个template-id(一个后面带有尖括号内部实参列表的模板名称),那么就应该使用关键字typename。

using指示符可以让以前不能访问发成员现在变成可访问的:
class BX {
public:
    void f(int);
    void f(char const*);
    void g();
};

class DX : private BX {
public:
    using BX::f;
};

如果期望使用using指示符所引入的依赖型名称是一个类型,必须插入关键字typename来显式指定:
template <typename T>
class BXT {
public:
    typename T Mystery;
    template <typename U> struct Magic;
};

template <typename T>
class DXTT : private BXT<T> {
public:
    using typename BXT<T>::Mystery;
    Mystery* p;
};

派生和类模板:
非依赖型基类:
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() { basefield = 0; }
};
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值