C++ Templates:深入模板基础

来源:互联网 发布:mysql存储过程怎么调用 编辑:程序博客网 时间:2024/05/20 01:13

参数化声明:
成员函数模板不能被声明为虚函数,因为虚函数调用机制的普通实现都使用了一个大小固定的表,每个虚函数都对应表的一个入口。然而,成员函数模板的实例化个数,要等到整个程序都翻译完毕才能够确定,这就和表的固定大小发生了冲突。

类模板不能和另外一个实体共享一个名称,这一点和class类型是不同的:
int x;
template <typename T> class x;    //错误

struct x;
template <typename T> class x;    //错误

模板名字是具有链接的,但它们不能具有C链接:
extern "C++" template <typename T> void normal();    //缺省情况
extern "C" template <typename T> void invalid();    //错误
由于外部链接,不能在函数内部声明函数。

基本模板:没有在模板名称后面添加一对尖括号(和里面实参)的声明:
template <typename T> class Box;    //正确,基本模板
template <typename T> class Box<T>;    //错误

模板参数:

存在3种模板参数:
1.类型参数
2.非类型参数
3.模板的模板参数

非类型参数:
非类型参数表示的是:在编译期或链接期可以确定的常值,这些参数的类型必须是下面的一种:

  • 整型或枚举类型
  • 指针类型(包含普通对象的指针类型、函数指针类型、指向成员的指针类型)
  • 引用类型(指向对象或指向函数的引用都是允许的)

    非类型模板参数的声明和变量的声明很相似,但它们不能具有static、mutable等修饰符;只能具有const和volatile限定符。但如果这两个限定符限定的如果是最外层的参数类型,编译器将会忽略它们。
非类型模板参数只能是右值,它们不能被取址,也不能被赋值。

模板的模板参数:
对于模板的模板参数而言,它的参数名称只能被自身其他参数的声明使用:
template <template<typename T> class List>
class Node {
    static T* storage;    //模板的模板参数在这里不能被使用
    ...
}

缺省模板实参:
只有类模板声明才能具有缺省模板实参,缺省实参不能依赖于自身的参数,但可以依赖于前面的参数。
对于任一个模板参数,只有在之后的模板参数都提供了缺省实参的前提下,才能具有缺省模板实参。
缺省实参不能重复声明。

模板实参:
可以通过下面几种机制来确定模板实参:

  • 显式模板实参
  • 注入式类名称
  • 缺省模板实参
  • 实参演绎


SFINAE原则:替换失败并非错误原则
SFINAE原则保护的只是:允许试图创建无效的类型,但并不允许试图计算无效的表达式,下面是个错误的例子:
template<int I> void f(int (&)[24/(4 - I)]);
template<int I> void f(int (&)[24/(4 + I)]);
int main() {
    &f<4>;    //错误,替换后第一个除数等于(不能应用SFINAE)
}

类型实参:
模板的类型实参是一些用例指定模板类型实参的值,平时使用的大多数leiixng都可以被用作模板的类型实参,但两种情况例外:
1.局部类和局部枚举不能作为模板的类型实参。
2.未命名的class类型或未命名的枚举类型不能作为模板的类型实参
尽管其他的类型都可以用作模板实参,但前提是该类型替换模板参数之后获得的构造必须是有效的。

非类型实参:
必须是以下几种中的一种:

  • 某一个具有正确类型的非类型模板参数
  • 一个编译期整型常值(或枚举)
  • 前面有单目运算符&的外部变量或者函数的名称
  • 对于引用类型的非类型模板参数,前面没有&运算符的外部变量和外部函数也是可取的
  • 一个指向成员的指针常量

当实参匹配“指针类型或者引用类型的参数”时,用户定义的类型转换和由派生类到基类的类型转换,都是不会被考虑的;即使在其他的情况下,这些隐式类型转换是有效的,但在这里都是无效的。
下面是一些有效的非类型模板实参的例子:

Code:
  1. template <typename T, T nontype_param>  
  2. class C;  
  3.   
  4. C<int, 33>* c1; //整型  
  5.   
  6. int a;  
  7. C<int*, &a>* c2;    //外部变量的地址  
  8.   
  9. void f();  
  10. void f(int);  
  11. C<void(*)(int), f>* c3; //函数名称:在这个例子中,重载解析会选择f(int),f前面的&隐式省略了  
  12.   
  13. class X {  
  14. public:  
  15.     int n;  
  16.     static bool b;  
  17. };  
  18.   
  19. C<bool&, X::b>* c4; //静态类成员是可取的变量名称  
  20.   
  21. C<int X::*, &X::n>* c5; //指向成员的指针常量  
  22.   
  23. template<typename T>  
  24. void temp1_func();  
  25.   
  26. C<void(), &temp1_func<double> >* c6;    //函数模板实例同时也是函数  

有些常值不能作为有效的非类型实参,包括:

  • 空指针常量
  • 浮点型值
  • 字符串

解决办法如下:
template <char const* str>
class Message;

extern char const hello[] = "Hello World!";

Message<hello>* hello_msg;

下面是一些错误的例子:

Code:
  1. template<typename T, T nontype_param>  
  2. class C;  
  3.   
  4. class Base {  
  5. public:  
  6.     int i;  
  7. } base;  
  8.   
  9. class Derived : public Base {  
  10. } derived_obj;  
  11.   
  12. C<Base*, &derived_obj>* err1;   //错误,这里不会考虑派生类到基类的类型转换  
  13.   
  14. C<int&, base.i>* err2;  //错误,域运算符后面的变量不会被看成变量  
  15.   
  16. int a[10];  
  17. C<int*, &a[0]>* err3;   //错误,单一数组元素的地址是不可取的  

实参的等价性:
当每个对应实参值都相等时,就称这两组模板实参是相等的:
Mix<int, 3*3>* p1;
Mix<int, 4+5>* p2;

友元:

如果要把类模板的实例声明为其他类(或者类模板)的友元,该类模板在声明的地方必须是可见的。
通过确认紧接在友元函数名称后面的是一对尖括号,我们可以把函数模板的实例声明为友元:
template <typename T1, typename T2>
void combine(T1, T2);

class Mixer {
    friend void combine<>(int&, int&);
};
不能在友元声明中定义一个模板实例,最多只能定义一个特化。

如果名称后面没有紧跟一对尖括号,那么只有在下面两种情况是合法的:
1.如果名称不是受限的,那么该名称一定不是引用一个模板实例。如果在友元声明的地方,还看不到所匹配的非模板函数(即普通函数),那么这个友元声明就是函数的首次声明,于是,该声明可以是定义。
2.如果名称是受限的,那么该名称必须引用一个在此之前声明的函数或者函数模板。在匹配过程中,匹配的函数要优先于匹配的函数模板,然而,这样的友元声明不能是定义。
例:

Code:
  1. void multiply (void*);  //普通函数  
  2.   
  3. template <typename T>  
  4. void multiply(T);       //函数模板  
  5.   
  6. class Comrades {  
  7.     friend void multiply(int) { }   //定义了一个新的函数::multiply(int),非受限函数名称,不能引用函数实例  
  8.     friend void ::multiply(void*);  //引用上面的普通函数  
  9.     friend void ::multiply(int);    //引用一个模板实例  
  10.     friend void ::multiply<double*>(double*);   //受限名称可以具有一对尖括号,但模板再次必须是可见的  
  11.     friend void ::error() { }       //错误,受限的友元不能是一个定义  
  12. };  

在模板内部定义的友元函数的类型定义中,必须包含类模板的模板参数,否则友元函数会被生成多次,违反了ODR原则:
template <typename T>
class Creator {
    friend void feed(Creator<T>*) {        //每个T都生成一个不同的::feed()函数
        ...
    }
};

友元模板:
只有在友元模板声明的是一个非受限的函数名称,并且后面没有紧跟尖括号的情况下,该友元模板声明才能成为定义。
友元模板声明的只是基本模板和基本模板的成员。当进行这些声明之后,与该基本模板相对应的模板局部特化和显式特化都会被自动地看成友元。

Code:
  1. class Manager {  
  2.     template <typename T>  
  3.     friend class Task;  
  4.     template <typename T>  
  5.     friend void Schedule<T>::dispatch(Task<T>*);  
  6.     template <typename T>  
  7.     friend int ticket() {  
  8.         return ++Manager::counter;  
  9.     }  
  10.     static int counter;  
  11. };