模板定义以关键字template开始,后接模板形参表(template parameter list),模板形参表是用尖括号括住的一个或者多个模板形参的列表,形参之间以逗号分隔。模板形参可以是表示类型的类型形参(type parameter),也可以是表示常量表达式的非类型形参(non-type parameter)。非类型形参跟在类型说明符之后声明。类型形参跟在关键字class或typename之后声明。

使用模板时,可以在模板名字后面显式给出用尖括号括住的模板实参列表(template argument list)。对模板函数或类的模板成员函数,也可不显式给出模板实参,而是由编译器根据函数调用的上下文推导出模板实参,这称为模板参数推导

函数模板

以下以取最大值的函数模板maximum为例。此函数在编译时会自动产生对应参数类型的代码,而不用显式声明。


#include <iostream>
 
template <typename T>
inline const T& maximum(const T& x,const T& y)
{
   if(y > x){
      return y;
   }
   else{
      return x;
   }
}
 
int main(void)
{
   using namespace std;
   //Calling template function
   std::cout << maximum<int>(3,7) << std::endl;         //输出 7
   std::cout << maximum(3, 7) << std::endl;             //自动补充类型声明
   std::cout << maximum<double>(3.0,7.0) << std::endl;  //输出 7
   return 0;
}


类模板


以下以將元件指標的操作,封裝成类別模板ComPtr為例。

#pragma once
 
template <typename Ty>
class ComPtr
{
protected:
    Ty* m_ptr;
 
public:
 
    ComPtr()
    {
        m_ptr = NULL;
    }
 
    ComPtr(const ComPtr& rhs)
    {
        m_ptr = NULL;
        SetComPtr(rhs.m_ptr);
    }
 
    ComPtr(Ty* p)
    {
        m_ptr = NULL;
        SetComPtr(p);
    }
 
    ~ComPtr()
    {
        Release();
    }
 
    const ComPtr& operator=(const ComPtr& rhs)
    {
        SetComPtr(rhs.m_ptr);
        return *this;
    }
 
    Ty* operator=(Ty* p)
    {
        SetComPtr(p);
        return p;
    }
 
    operator Ty* ()
    {
        return m_ptr;
    }
 
    Ty* operator->()
    {
        return m_ptr;
    }
 
    operator Ty** ()
    {
        Release();
        return &m_ptr;
    }
 
    operator void** ()
    {
        Release();
        return (void**)&m_ptr;
    }
 
    bool IsEmpty()
    {
        return (m_ptr == NULL);
    }
 
    void SetComPtr(Ty* p)
    {
        Release();
 
        m_ptr = p;
        if (m_ptr)
        {
            m_ptr->AddRef();
        }
    }
 
    void Release()
    {
        if (m_ptr)
        {
            m_ptr->Release();
            m_ptr = NULL;
        }
    }
};



模板的嵌套:成员模板

对于类中的模板成员函数、嵌套的成员类模板,可以在封闭类的内部或外部定义它们。当模板成员函数、嵌套类模板在其封闭类的外部定义时,必须以封闭类模板的模板参数(如果它们也是模板类)和成员模板的模板参数开头。[1]如下例:


template <typename C> class myc{
  public:
    template <typename S> C foo(S s);
};
 
//下行需要给出外部类与内部嵌套类的模板形参列表:
template<typename C> template <typename S> C myc<C>::foo(S s){
C var;
return var;   
}
 
int main()
{
float f;
myc<int> v1;
v1.foo(f);
}


C++标准规定:如果外围的类模板没有特例化,里面的成员模板就不能特例化。例如:


template <class T1> class A {
  template<class T2> class B {
      template<class T3> void mf1(T3);
      void mf2();
  };
};
 
template <> template <class X>
   class A<int>::B {
      template <class T> void mf1(T);
   };
 
template <> template <> template<class T>
    void A<int>::B<double>::mf1(T t) { }
 
template <class Y> template <>
     void A<Y>::B<double>::mf2() { } // ill-formed; B<double> is specialized but its enclosing class template A is not


依赖名字与typename关键字[编辑]

一个模板中的依赖于一个模板参数(template parameter)的名字被称为依赖名字 (dependent name)。当一个依赖名字嵌套在一个类的内部时,称为嵌套依赖名字(nested dependent name)。一个不依赖于任何模板参数的名字,称为非依赖名字(non-dependent name)。[3]

编译器在处理模板定义时,可能并不确定依赖名字表示一个类型,还是嵌套类的成员,还是类的静态成员。C++标准规定:如果解析器在一个模板中遇到一个嵌套依赖名字,它假定那个名字不是一个类型,除非显式用typename关键字前置修饰该名字。[4]

typename关键字有两个用途:

  1. 常见的在模板定义中的模板形参列表,表示一个模板参数是类型参数。等同于使用class。
  2. 使用模板类内定义的嵌套依赖类型名字时,显式指明这个名字是一个类型名。否则,这个名字会被理解为模板类的静态成员名。C++11起,这一用途也可以出现在模板以外,尽管此时

typename

  1. 关键字不是必要的。

在下述情形,对嵌套依赖类型名字不需要前置修饰typename关键字:[5]

  • 派生类声明的基类列表中的基类标识符;
  • 成员初始化列表中的基类标识符;

class

struct

enum

  • 等关键字开始的类型标识符

因为它们的上下文已经指出这些标识符就是作为类型的名字。例如:


template <class T> class A: public T::Nested { //基类列表中的T::Nested
  public:
    A(int x) : T::Nested(x) {}; //成员初始化列表中的T::Nested
    struct T::type1 m; //已经有了struct关键字的T::type1
};
 
class B{
  public:
    class Nested{
      public:
           Nested(int x){};
    };
    typedef struct {int x;} type1;
};
 
int main()
{
  A<B> a(101);
  return 0;
}



template关键字(重要)

template关键字有两个用途:

  1. 常见的在模板定义的开始。
  2. 模板类内部定义了模板成员函数或者嵌套的成员模板类。在模板中,当引用这样的模板成员函数或嵌套的成员模板类时,可以在

::

  1. (作用域解析)运算符、

.

  1. (以对象方式访问成员)运算符、

->

  1. (以指针方式访问成员)运算符之后使用template关键字,随后才是模板成员函数名字或嵌套的成员模板类名字,这使得随后的左尖括号

<

  1. 被解释为模板参数列表的开始,而不是小于号运算符。C++11起,这一用途也可以出现在模板以外,尽管此时

template

  1. 关键字不是必要的。例如:
class A { public:
    template <class U> class B{
         public: typedef int INT;
    };
    template <class V> void foo(){}
};
 
template <typename T>
int f()
{
  typename T::template B<double>::INT i;
  i=101;
  T a, *p=&a;
  a.template foo<char>();
  p->template foo<long>();
  return 0;
}
 
int main()
{
  f<A>();
  A::B<double>::INT i;  // 自C++11起,也可写作typename A::template B<double>::INT i;
}