转:《C++ Templates》读书笔记

《C++ Templates》读书笔记(一):模板参数
2009-08-17 12:48

有三种模板参数(形参):

(1)类型参数(这是使用得最多的)
(2)非类型参数
(3)模板的模板参数
类型参数:

  类型参数是通过关键字typename或者class引入。关键字后面必须是一个简单的标识符,后面用逗号来隔开下一个参数声明,等号代表接下来的是缺省模板实参,一个封闭的尖括号(>)表示参数化子句的结束。

  在模板声明内部,类型参数的作用类似于typedef名称。例如,如果T是一个模板参数,就不能使用诸如class T等形式的修饰名称,即使T是一个要被class类型替换的参数也不可以。

template <typename Allocator>
class List {
      class Allocator* allocator;             // Error
      friend class Allocator;                   // Error
      …
};
非类型参数:

   非类型参数表示的是:在编译期或链接期可以确定的常值(模板的模板参数也不属于类型模板参数,但讨论非类型模板参数时,并不考虑模板的模板参数)。这种参数的类型必须是下面的一种:

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

  所有其他的类型现今都不允许作为非类型参数使用(但在将来很可能会增加浮点数类型)。在某些情况下,非模板参数的声明也可以使用关键字typename

template <typename T, typename T::Allocator* Allocator>
class List;

  这两个参数的区别在于:第1个typename后面是一个简单标识符T,而第2个typename后面是一个受限的名称(即是一个包含双冒号 :: 的名称),它表示Allocator是属于T的一个子类型。

   函数和数组类型也可以被指定为非模板参数,但要把它们先隐式地转换为指针类型,这种转型也成为decay

template <int buf[5]> class Lexer;               // buf实际上是一个int* 类型
template <int* buf> class Lexer;                  // 这是上面的重新声明

  非类型模板参数的声明和变量的声明很相似,但它们不能具有staticmutable等修饰符;只能具有constvolatile[1]限定符。但如果这两个限定符限定的如果是最外层的参数类型,编译器将会忽略它们:

template <int const length> class Buffer;           // const在这里是没用的,被忽略了
tempalte <int length> class Buffer;                    // 和上面等价

  最后,非类型模板参数只能是右值:它们不能被取址,也不能被赋值。

模板的模板参数:

    模板的模板参数是代表类模板的占位符。它的声明和类模板的声明很类似,但不能使用关键字structunion

 template <template<typename X> class C>     //正确
 void f(C<int>* p);

 template <template<typename X> struct C>     //错误
 void f(C<int>* p);

 template <template<typename X> union C>     //错误
 void f(C<int>* p);

  在它们声明的作用域中,模板的模板参数的用法和类模板的用法很类似。模板的模板参数的参数(如下面的A)可以具有缺省模板实参。显然,只有在调用时没有指定该参数的情况下,才会应用缺省模板实参:

template <template<typename T, typename A = MyAllocator> class Container>
class Adaptation {
       Container<int> storage;           // 隐式等同于 Container<int, MyAllocator>
       …
};
  对于模板的模板参数而言,它的参数名称只能被自身其他参数的声明使用。下面的假设例子说明了这一点:
template <template<typename T, T*> class Buf>
class Lexer {
      static char storage[5];
      Buf<char, &Lexer<Buf>::storage[0]> buf;
      …
};
template <template<typename T> class List>
class Node {
      static T* storage;           // 错误:模板的模板参数的参数在这里不能被使用
      …
};
   通常而言,模板的模板参数的参数名称(如上面的例子)并不会在后面被用到。因此,该参数也经常被省略不写,即没有命名。例如,前面的Adaptation模板的例子可以这样声明:
template <template<typename, typename = MyAllocator> class Container>
 class Adaptation {
       Container<int> storage;          // 隐式等同于 Container<int, MyAllocator>
       …
};
缺省模板实参:

    现今,只有类模板声明才能具有缺省模板实参。任何类型的模板参数都可以拥有一个缺省实参,只要该缺省实参能够匹配这个参数就可以。显然,缺省实参不能依赖于自身的参数;但可以依赖于前面的参数:

template <typename T, typename Allocator = allocator<T> >
class List;              // 就是说,allocator<T>不能依赖于本身参数Allocator,但是能依赖于前面参数T

       与缺省的函数调用参数的约束一样,对于任一个模板参数,只有在之后的模板参数都提供了缺省实参的前提下,才能具有缺省模板实参。后面的缺省值通常是在同个模板声明中提供的,但也可以在前面的模板声明中提供。下面的例子说明了这一点:

template <typename T1, typname T2, typename T3,
                       typename T4 = char, typename T5 = char>
class Quintuple;                        // 正确

template <typename T1, typname T2, typename T3 = char,
                       typename T4, typename T5>
class Quintuple;                        // 正确,根据前面的模板声明,T4和T5已经具有缺省值了

template <typename T1 = char, typname T2, typename T3,
                       typename T4, typename T5>
class Quintuple;                        // 错误,T1不能具有缺省实参,因为T2还没有缺省实参
       另外,缺省实参不能重复声明:
template <typename T = void>
class Value;
template <typename T = void>
class Value;                               // 错误,重复出现的缺省实参
----------------------------------------------------------------------------------------------------------------------------------

[1] 关于volatile,它的作用在于限定一个对象可以被外部进程(操作系统、硬件或并发进程等)改变,声明的语法如下:    
   int volatile nVint;

参考:http://hi.baidu.com/b1ghost/blog/item/daf6b7445f10392fcffca392.html

 

《C++ Templates》读书笔记(二):模板实参
2009-08-17 17:45

模板实参是指:在实例化模板时,用来替换模板参数的值。我们可以使用下面几种不同的机制来确定这些值。

类型实参:

       模板的类型实参是一些用来指定模板类型参数的值。我们平时使用的大多数类型都可以被用作模板的类型实参,但有两种情况例外:

       1. 局部类和局部枚举(换句话说,指在函数定义内部声明的类型)不能作为模板的类型实参。
       2. 未命名的class类型或者未命名的枚举类型不能作为模板的类型实参(然而,通过typedef声明给出的未命名类和枚举是可以作为模板类型实参的)。

       下面的例子很好地说明了这两种例外情况:

       template <typename T> class List {
             …
       };

       typedef struct {
              double x, y, z;
       } Point;

       typedef enum { red, green, blue } *ColorPtr;

       int main()
       {
              struct Association                             // 局部类型
              {
                      int* p;
                       int* q;
              };
              List<Association*> error1;                // 错误:模板实参中使用了局部类型
              List<ColorPtr> error2;                      // 错误:模板实参中使用了未命名的类型,因为typedef定义
                                                                       // 的是*ColorPtr,不是ColorPtr
              List<Point> ok;                                 // 正确:通过使用typedef定义的未命名类型
       }

   通常而言,尽管其他的类型都可以用作模板实参,但前提是该类型替换模板参数之后获得的构造必须是有效的。
tempalte <typename T>
void clear (T p)
{
       *p = 0;                // 要求单目运算符*可以用于类型T
}
int main()
{
      int a;
      clear(a);               // 错误:int类型并不支持但不运算符*
}
非类型实参:

       非类型模板实参是那些替换非类型参数的值。这个值必须是以下几种中的一种:

       1. 某一个具有正确类型的非类型模板参数
       2. 一个编译期整型常数(或枚举值)。这只有在参数类型和值的类型能够进行匹配,或者值的类型可以隐式地转换为参数类型(例如,一个char值可以作为int参数的实参)的前提下,才是合法的。
       3. 前面有单目运算符&(即取址)的外部变量或者函数的名称。对于函数或数据变量,&运算符可以省略。这类模板实参可以匹配指针类型的非类型参数。
       4. 对于引用类型的非类型模板参数,前面没有&运算符的外部变量和外部函数也是可取的。
       5. 一个指向成员的指针常量。换句话说,类似&C::m的表达式,其中C是一个class类型,m是一个非静态成员(成员变量或者函数)。这类实参只能匹配类型为“成员指针”的非类型参数。

       当实参匹配“指针类型或者引用类型的参数”时,用户定义的类型转换(例如单参数的构造函数和重载类型转换运算符)和由派生类到基类的类型转换,都是不会被 考虑的;即使在其他情况下,这些隐式类型转换时有效的,但在这里都是无效的。隐式类型转换的唯一应用只能是:给实参加上关键字const或者volatile。

       下面是一些有效的非类型模板实参的例子:

tempalte <typename T, T nontype_parm>
class C;
C<int, 33>* c1;               // 整型
int a;
C<int*, &a>* c2;              // 外部变量地址
void f();
void f(int);
C<void(*)(int), f>* c3;       // 函数名称。在这个例子中,重载解析会选择f(int),f前面的&隐式省略了
class X {
 public:
        int n;
        static bool b;
};
C<bool&, X::b>* c4;        // 静态类成员是可取的变量(和函数)名称
       C<int X::*, &X::n>* c5;      // 指向成员的指针常量
       template <typename T>
       void templ_func();
 C<void(), &templ_func<double> >* c6;        // 函数模板实例同时也是函数
    模板实参的一个普遍约束是:在程序创建的时候,编译器或者链接器要能够确定实参的值。如果实参的值要等到程序运行时才能够确定(譬如,局部变量的地址),就不符合“模板是在程序创建的时候进行实例化”的概念了。

       另一方面,有些常值不能作为有效的非类型实参,这也许会令人觉得很诧异。这些常值包括:

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

       有关字符串的一个问题就是:两个完全等同的字符串可以存储在两个不同的地址中。在此,我们用一种(很笨的)解决方法来表达需要基于字符串进行实例化的模板:引入一个额外的变量来存储这个字符串。

template <char const* str>
class Message;
extern char const hello[] = "Hello World!";
Message<hello>* hello_msg;

 可以看到,我们使用了关键字extern。因为如果不使用这个关键字,上面的const数组变量具有内部链接。下面给出一些错误的例子:

template <typename T, T nontype_parm>
class C;
class Base {
      public:
             int i;
} base;
class Derived: public Base {
} derived_obj;
C<Base*, &derived_obj>* err1;       // 错误,这里不会考虑派生类到基类的类型转换
C<int&, base.i>* err2;                     // 错误,域运算符(.)后面的变量不会被看成变量
int a[10];
 C<int*, &a[0]>* err3;                       // 错误,单一数组元素的地址并不是可取的

模板的模板实参:

   “模板的模板实参”必须是一个类模板,它本身具有参数,该单数必须精确匹配它“所替换的模板的模板参数”本身的参数。在匹配过程中,“模板的模板实参”的 缺省模板实参将不会考虑(但是如果“模板的模板参数”具有缺省实参,那么模板的实例化过程是会考虑模板的模板参数的缺省实参的)。
       这段看起来很绕,看下面例子能更易理解。

#include <list>
       // List 的声明:
       //      namespace std {
       //             template <typename T, typename Allocator = allocator<T> >
        //             class list;
       //       }

 tempalte <typename T1, typename T2,
                             tempalte<typename> class Container>
                                                     // Container期望的是只具有一个参数的模板
 class Relation {
    public:
         …
    private:
         Container<T1> dom1;
         Container<T2> dom2;
 };
 int main()
 {
         Relation<int, double, std::list> rel;    // 错误,std::list是一个具有两个参数的模板
         …
  }
   这里的问题是:标准库中的std::list模板具有两个参数,它的第2个参数(我们称之为内存配置器allocator)具有一个缺省值;但当我们匹配std::listContainer参数时,事实上并不会考虑这个缺省值(即认为缺省值并不存在)。

  有时,我们可以通过给模板的模板参数添加一个具有缺省值的参数,来解决这个问题。在前面的例子中,我们可以这样改写Relation模板:

#include <memory>
tempalte <typename T1, typename T2,
                             tempalte<typename T, typename = std::allocator<T> > class Container>
                                              // Container现在就能够接受一个标准容器模板了
class Relation {
    public:
         …
    private:
         Container<T1> dom1;
         Container<T2> dom2;
 };
  显然,这并不是一个令人满意的解决方案,但它可以让标准容器模板得到使用。另外我们注意到了一个事实:从语法上讲,只有关键字class才能被用来声明模板的模板参数;但是这并不意味着只有用关键字class声明的类模板才能作为它的替换实参。实际上,“struct模板”、“union模板”都可以作为模板的模板参数的有效实参。这和我们前面所提到的事实很相似:对于用关键字class声明的模板类型参数,我们可以用(满足约束的)任何类型作为它的替换实参。

 

参考:http://hi.baidu.com/b1ghost/blog/item/daf6b74458043e2fcffca38e.html

 

posted @ 2012-06-22 09:04  Mr.Rico  阅读(...)  评论(...编辑  收藏