C++言語解説:2-4.テンプレート
2002-07-28

[概要] プログラムコードを再利用するために用意された機能がテンプレートです。テン
    プレートの構造について学習します。
    
[構成]・汎用関数
     * 型が違うだけなら
     * 汎用関数のオーバーロード
     * template指定のオーバーロード
     * 仮引数の指定
   ・汎用クラス
     * 汎用クラスの定義
     * 非汎用引数指定
     * 型のデフォルト指定


汎用関数

 型が違うだけなら
 
  ・C++には関数のオーバーロードと呼ばれる機能があります。これは以前(1-6章.関数
   の機能)説明しましたが、例えば
     (a + b)^2
   の演算を行う関数CalFunc()を各型のデータへ対応させるため
     int CalFunc(int a, int b);
     double CalFunc(double a, double b);
   のように、関数をオーバーロード定義しました。
  
  ・この場合、関数の中身は全く変わりません。恐らく「もっと汎用的に書ければ簡単
   なのに」と思った人もいることでしょう。C++では汎用関数(generic function)と呼
   ばれる機能でこれを実現することができます。
  
  ・汎用関数は、以下の形式で定義します。
     template <class 型引数> 戻値型 関数名(仮引数リスト){・・・}
   型引数の部分には、まさに型が入ります。List1に汎用関数の例を示します。
  
   [List1.汎用関数の例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    template <class T>
    T CalFunc(T a, T b)  //template文と関数名定義に改行あってもOK
    {
      retuen (a*a + b*b + 2*a*b);
    }
    
    int main()
    {
      cout << CalFunc(1,2) << endl;
      cout << CalFunc(1.1, 2.3) << endl;
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     9
     11.56
   └───────────────────────────────────┘
  
  ・templateキーワードと関数名定義の間に改行が入っても問題有りません。汎用関数
   の型引数は、カンマ演算子により複数定義できるので、このような場合は改行表現
   することが多くなります。

   [List2.汎用関数(複数型)の例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    template <class T1, class T2>
    void TypeDisp(T1 a, T2 b)
    {
      cout << "T1:" << a << endl;
      cout << "T2:" << b << endl;
      return;
    }
    
    int main()
    {
      TypeDisp(1.0, 'a');
      TypeDisp("ABCD", 10L);
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     T1:1
     T2:a
     T1:ABCD
     T2:10
   └───────────────────────────────────┘
  

 汎用関数のオーバーロード
 
  ・汎用関数では型に従って関数が生成されます。しかし「ある型の場合は実装を変え
   たい」というケースもあるでしょう。この場合、特別Versionの汎用関数を作ること
   でオーバーロード
(*1)ができます。以下の書式で定義します。
     template<> 戻値型 関数名<特定型>(引数リスト){・・・}
  
  ・このような関数を原著ではユーザ定義特別版(user-defined specialization)と呼ん
   でいます。List3に例を示します。
   
   [List3.汎用関数の特別Version(明示的特定)]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    template <class T> T Func(T a) //通常汎用関数
    {
      cout << "Template : ";
      return a;
    }
    
    template<> int Func<int>(int a) //int型特別Version
    {
      cout << "Special : ";
      return a;
    }
    
    int main()
    {
      cout << Func("ABCD") << endl;
      cout << Func(10) << endl;
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     Template : ABCD
     Special : 10
   └───────────────────────────────────┘
   
   
 template指定のオーバーロード
 
  ・関数オーバロードと同様に、template指定自体をオーバーロードすることも可能で
   す。List4に例を示します。

   [List4.template指定のオーバーロード]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    template <class T> void Func(T a)
    {
      cout << "Template1 : ";
      cout << "a:"<< a << endl;
      return;
    }
    
    template<class T1, class T2>
    void Func(T1 a, T2 b)
    {
      cout << "Template2 : ";
      cout << "a:"<< a << " ";
      cout << "b:"<< b << endl;
      return;
    }
    
    int main()
    {
      Func('a');
      Func(10);
      Func(10.5, 5);
      Func("ABCD", 6);
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     Template1 : a:a
     Template1 : a:10
     Template2 : a:10.5 b:5
     Template2 : a:ABCD b:6
   └───────────────────────────────────┘

 
 仮引数の指定
 
  ・汎用関数の仮引数指定には、特定の型を混在させることができます。List5に例を示
   します。
 
   [List5.仮引数の特定型混在指定]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    template <class T> void Func(T a, int i_a)
    {
      cout << "Template1 : ";
      cout << "a:"<< a << " ";
      cout << "int:" << i_a << endl;
      return;
    }

    int main()
    {
      Func("ABCD", 10);
      Func(5,20);
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     Template1 : a:ABCD int:10
     Template1 : a:5 int:20
   └───────────────────────────────────┘
   
  ・デフォルト引数などを使用した際に、関数指定が曖昧になる可能性については、関
   数オーバーロードを使用するときと同じようなケアが必要
です。

   [List6.指定が曖昧 (*コンパイルエラーになる)]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    template <class T> void Func(T a, int i_a = 0)
    {
      cout << "Template1 : ";
      cout << "a:"<< a << " ";
      cout << "int:" << i_a << endl;
      return;
    }

    template <class T> void Func(T a)
    {
      cout << "Template2 : ";
      cout << "a:"<< a << " ";
      return;
    }

    int main()
    {
      Func("ABCD");
      
      return 0;
    }
   └───────────────────────────────────┘


汎用クラス

 汎用クラスの定義
 
  ・クラスについても関数と同様に、汎用クラス(generic class)を定義することができ
   ます。汎用クラスは以下のように記述します。
     template <class 型引数> class クラス名 {・・・};
 
  ・汎用クラスの場合は関数と違い、オブジェクト生成時に引数が与えられるとは限ら
   ないので、型指定を伴ったオブジェクト定義を以下の形式で行います。
     クラス名<型名> オブジェクト名;
  
  ・classブロックの外で定義するメンバ関数については、クラス名部に型指定記述が追
   加
されます。
     temaplte <class 型引数> 戻値型 クラス名<型引数>::関数名(引数リスト)
     {・・・}
   List7に汎用クラスの使用例を示します。
 
   [List7.汎用クラスの使用例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    template <class T> class AddSum {
       T* mt_data;
       int mi_index;
      public:
       AddSum(int i_index);
       ~AddSum();
       void InData(int i_index, T t_data);
       T GetSum();
    };

    template <class T> AddSum<T>::AddSum(int i_index)
    {
      mi_index = i_index;
      mt_data = new T [mi_index];
      for (int i=0; i<mi_index; i++){
        *(mt_data+i) = (T)0;
      }
      return;
    }
    
    template <class T> AddSum<T>::~AddSum()
    {
      delete[] mt_data;
      return;
    }
    
    template <class T> void AddSum<T>::InData(int i_index, T t_data)
    {
      if (i_index < mi_index) *(mt_data+i_index) = t_data;
      return;
    }
    
    template <class T> T AddSum<T>::GetSum()
    {
      T t_sum = (T)0;
      for (int i=0; i<mi_index; i++) {
        t_sum += *(mt_data+i);
      }
      
      return t_sum;
    }
    
    int main()
    {
      AddSum<int> int_obj(3);
      int_obj.InData(0, 10);
      int_obj.InData(1, 20);
      int_obj.InData(2, 30);
      
      AddSum<double> double_obj(3);
      double_obj.InData(0, 10.2);
      double_obj.InData(1, 11.6);
      double_obj.InData(2, 24.93);
      
      cout << int_obj.GetSum() << endl;
      cout << double_obj.GetSum() << endl;
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     60
     46.73
   └───────────────────────────────────┘
   
  ・List7では1個の型引数を利用していますが、カンマ演算子により複数の型引数を指
   定できる
ことは、汎用関数と変わりません。
   
  ・また、その他の機能
     + 特定型に対する特別Versionを作成できる点、
     + templateのオーバーロードが適用できる
   等の点についても汎用関数と同様に使用することができます。
  

 非汎用引数指定
 
  ・汎用クラスでは、型指定部に非汎用の引数を指定することができます。例えば以下
   のように
     template <class T, int i_a> class クラス名 {・・・};
   この場合、オブジェクト定義は
     クラス名<double, 10> A;
   のように記述することができます。
 
  ・非汎用引数部はデフォルト引数を適用することもできます。例えば
     template <class T, int i_a=10> class クラス名 {・・・};
   であれば
     クラス名<double> A; //省略時 i_a = 10;
   と記述することができます。


 型のデフォルト指定
 
  ・汎用クラスの場合、型指定が省略された場合のデフォルト型を用意することができ
   ます。
デフォルト型は以下のように指定します。
     temaplte <class T=デフォルト型名> class クラス名 {・・・};

  ・その際、以下のようにオブジェクト指定を行うことが可能です。
     class<> オブジェクト名;
   List9に使用例を示します。

   [List9.クラスのデフォルト型指定使用例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    template <class T=int> class AddSum {
       T* mt_data;
       int mi_index;
      public:
       AddSum(int i_index);
       ~AddSum();
       void InData(int i_index, T t_data);
       T GetSum();
    };
      :
      : //省略 List7と同様
      :
    int main()
    {
      AddSum<> int_obj(3); //型指定省略
      int_obj.InData(0, 10);
      int_obj.InData(1, 20);
      int_obj.InData(2, 30);
      
      AddSum<double> double_obj(3);
      double_obj.InData(0, 10.2);
      double_obj.InData(1, 11.6);
      double_obj.InData(2, 24.93);
      
      cout << int_obj.GetSum() << endl;
      cout << double_obj.GetSum() << endl;
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     60
     46.73
   └───────────────────────────────────┘


(*1)ここで紹介した特別Version(明示的特定)以外に、単純な関数のオーバーロードも適用
  できます。特別Versionは最近のC++で追加された機能であり、それまでは関数オーバ
  ロードで対応していたからです。

[Revision Table]
 |Revision |Date    |Comments
 |----------|-----------|-----------------------------------------------------
 |1.00   |2002-07-28 |初版
 |1.01   |2002-07-30 |リンク追加
[end]
Copyright(C) 2002 Altmo