テンプレート

ここではC++のテンプレートについて学ぶ。
※ 教科書の第11章「テンプレートと例外処理」の前半もよく読んでテンプレートに ついて理解を深めること。

テンプレート

テンプレートとは 一言で言えば「型をパラメータとして取り扱うことができる仕組み」 である。
(※ 厳密には型以外のものもパラメータ化できるが、ここでは型に限定して 説明する)
具体的にテンプレートが何であるかを説明する前に、 何故、テンプレートが必要であるかを以下の例で説明する。

ここで、与えられた引数の絶対値を返す関数を考えてみる。
例えば、int型の引数の絶対値を返す関数 abs_int() は以下のように なる。

  1 : //
  2 : // absolute value (int)
  3 : //
  4 : #include <iostream>
  5 : using namespace std;
  6 :
  7 : int abs_int(int v)
  8 : {
  9 :     return v >= 0? v: -v;
 10 : }
 11 : 
 12 : int main()
 13 : {
 14 :     int v = -9;
 15 :     cout << "v=" << v << "\n"
 16 :          << "abs(v)=" << abs_int(v) << endl;
 17 : 
 18 :     return 0;
 19 : }

次に、double型の引数の絶対値を返す関数 abs_double() を考えると 以下のようになる。
(※ もしここで、強引にint型用に定義された abs_int() にdouble型 の値を与えても小数点以下の値を切り捨てられてしまい、期待した結果が得ら れないことになる)

  1 : //
  2 : // absolute value (double)
  3 : //
  4 : #include <iostream>
  5 : using namespace std;
  6 : 
  7 : double abs_double(double v)
  8 : {
  9 :     return v >= 0? v: -v;
 10 : }
 11 : 
 12 : int main()
 13 : {
 14 :     double v = -9.5;
 15 :     cout << "v=" << v << "\n"
 16 :          << "abs(v)=" << abs_double(v) << endl;
 17 : 
 18 :     return 0;
 19 : }
ここで、abs_int()abs_double() の2つの関数を比較して みると、違いは引数と返り値の型のみである。
このようにプログラミングをすると「同じような記述」「似通った記述」に 遭遇することが少なくない。
しかし、同じことをする(よって記述内容も同じ)関数であっても型が異なるだ けで別の関数を定義する必要がある。
これではあまりにも非効率的であるため、C++言語ではテンプレートを使って 記述を 1つにまとめることが出来るようになっている。
上記の絶対値を返す関数をテンプレートを用いて記述すると以下のようになる。
  1 : //
  2 : // absolute value (generic)
  3 : //
  4 : #include <iostream>
  5 : using namespace std;
  6 : 
  7 : template <class X>
  8 : X absolute(X v) {
  9 :     return v >= 0? v: -v;
 10 : }
 11 : 
 12 : int main()
 13 : {
 14 :     int v = -9;
 15 :     double v2 = -9.5;
 16 : 
 17 :     cout << "v=" << v << "\n"
 18 :          << "abs(v)=" << absolute(v) << endl;
 19 : 
 20 :     cout << "v=" << v2 << "\n"
 21 :          << "abs(v)=" << absolute(v2) << endl;
 22 : 
 23 :     return 0;
 24 : }
7行目から10行目がテンプレートを用いた関数の定義である。
このように定義しておけば、コンパイラが自動的に与えられた型に対応した 関数を実体化(インスタンス化)してくれる。
上記の例では、18行目の関数 absolute() は引数としてint型の変数v を与えているので、int型に対応した関数 absolute() が自動的に作り 出されることになる。
同様に、21行目の関数 absolute() は引数としてdouble型の変数v2を 与えているので、double型に対応した関数 absolute() が自動的に作 り出されることになる。

「型をパラメータとして取り扱う」とはこのように型に依存しない形で汎用的 な記述を可能にするということである。
特定の型に依存しない汎用的な記述を目指したプログラミングを指して、 ジェネリックプログラミング (Generic Programming)と称することも あるが、C++言語ではテンプレート機能を用いることで、それが可能となる。
上記の例では、型に依存しない形で関数を定義しているため、 汎用関数 (Generic Function)と呼ぶ。
また、関数以外にもクラスの定義を型に依存しない形で行なうことができ、 これを汎用クラス (Generic Class)と呼ぶ。
以下、それぞれについて説明する。

なお、後述する STL (Standard Template Library) は、 このテンプレートの機能をフルに活用して実装されている。

汎用関数 (Generic Function)

汎用関数 (Generic Function)は型に依存しない形で定義された関数の ことである。
汎用関数テンプレート関数を用いて定義される。
テンプレート関数とは、型をパラメータとして持つ関数定義であり、 以下のようにして定義される。
template <class Ttype>
ret_type func_name ( parameter_list )
{
    // 関数の本体
}

テンプレート関数の呼び出しは以下のように記述する。

func_name<type_name> ( parameters );
ただし、引数などから型が推測可能な場合はtype_name (型名)を省略 することが出来、通常の関数呼び出しと同じように記述できる。
(この場合は、コンパイラが型を推測して自動的に適切な型の関数を生成する)
func_name ( parameters );

具体的な使用例は以下の通りである。

  1 : //
  2 : // maximum value (generic)
  3 : //
  4 : #include <iostream>
  5 : using namespace std;
  6 : 
  7 : template <class X>
  8 : X maximum(X v, X u) {
  9 :     return u >= v? u: v;
 10 : }
 11 : 
 12 : int main()
 13 : {
 14 :     int v = 2, v2 = 3;
 15 :     double d = 4.0, d2 = 4.1;
 16 : 
 17 :     cout << "max(" << v << "," << v2 << ") = "
 18 :          << maximum<int>(v, v2) << endl;
 19 : 
 20 :     cout << "max(" << d << "," << d2 << ") = "
 21 :          << maximum<double>(d, d2) << endl;
 22 : 
 23 :     return 0;
 24 : }
これは2つの引数の大きい方の値を返す関数をテンプレートを使って型に依存 しない形で定義したものである。

この例の場合は引数の型から型が推測可能であるため、省略できる。

  1 : //
  2 : // maximum value (generic)
  3 : //
  4 : #include <iostream>
  5 : using namespace std;
  6 : 
  7 : template <class X>
  8 : X maximum(X v, X u) {
  9 :     return u >= v? u: v;
 10 : }
 11 : 
 12 : int main()
 13 : {
 14 :     int v = 2, v2 = 3;
 15 :     double d = 4.0, d2 = 4.1;
 16 : 
 17 :     cout << "max(" << v << "," << v2 << ") = "
 18 :          << maximum(v, v2) << endl;
 19 : 
 20 :     cout << "max(" << d << "," << d2 << ") = "
 21 :          << maximum(d, d2) << endl;
 22 : 
 23 :     return 0;
 24 : }
実際の型がint型かdouble型かはコンパイラが自動的に判定して適切な関数を 生成してくれる。
(18行目と21行目の関数 maximum() に型の指定がないことに注意)

実行結果は以下の通りになり、どちらの場合も同じ結果となる。

max(2,3) = 3
max(4,4.1) = 4.1

汎用クラス (Generic Class)

汎用クラス (Generic Class)とは型に依存しない形で定義されたクラ スのことである。
汎用クラステンプレートクラスを用いて定義される。
テンプレートクラスとは、型をパラメータとして持つクラス定義であり、 以下のようにして定義される。
template <class Ttype>
class class_name {
    // クラスの中身
};
テンプレートクラスのインスタンス(つまり、テンプレートクラス 中の型が決定されたクラス)を生成する際は以下の通りに記述する。
class_name <type> object;

具体的な使用例は以下の通りである。

  1 : //
  2 : // generic stack class
  3 : //
  4 : #include <iostream>
  5 : using namespace std;
  6 : 
  7 : template <class X>
  8 : class Stack {
  9 :     X    *data;
 10 :     int  pos;
 11 :     int  max;
 12 : public:
 13 :     Stack(int size = 10) {
 14 :         data = new X[size];
 15 :         max = size;
 16 :         pos = 0;
 17 :     }
 18 : 
 19 :     void push(X v) {
 20 :         if (pos >= max) { cerr << "stack full\n"; exit(1); }
 21 :         data[pos++] = v;
 22 :     }
 23 :     X pop() {
 24 :         if (pos <= 0) { cerr << "stack empty\n"; exit(1); }
 25 :         return data[--pos];
 26 :     }
 27 : 
 28 :     bool empty() { return pos == 0; }
 29 :     bool full() { return pos == max; }
 30 : };
 31 : 
 32 : int main()
 33 : {
 34 :     Stack<int>    istack;    // int-type stack
 35 :     Stack<double> dstack;    // double-type stack
 36 : 
 37 :     istack.push(3);
 38 :     istack.push(1);
 39 :     istack.push(4);
 40 : 
 41 :     dstack.push(2.5);
 42 :     dstack.push(7.2);
 43 : 
 44 :     cout << "stack (int)\n";
 45 :     while (!istack.empty()) {
 46 :         cout << istack.pop() << "\n";
 47 :     }
 48 :     cout << "stack (double)\n";
 49 :     while (!dstack.empty()) {
 50 :         cout << dstack.pop() << "\n";
 51 :     }
 52 : 
 53 :     return 0;
 54 : }
これはスタック(先入れ後出しデータ)を格納するデータの型に依存しない形で 実現したクラス定義の例である。

7行目から30行目までがスタックをテンプレートクラスとして定義して いる部分である。
メンバ関数としてスタックにデータを入れる push()、 スタックからデータを取り出す pop()、 スタックが空であるか満杯であるかをチェックする empty()full() が定義されている。
このクラスは格納するデータの型とは独立に定義できていることが分かる。

34行と35行でそれぞれint型のスタックとdouble型のスタックのインスタンス を生成している。

実行結果は以下の通りになり、それぞれのデータ型のスタックとして正しく 動作しているのが分かる。
(スタック順 (先に入れたものが後から出てくる) にデータが表示されている)

stack (int)
4
1
3
stack (double)
7.2
2.5

練習問題

2つの引数の小さい方の値を返す関数 minimum()をテンプレートを使っ て型に依存しない形で定義せよ。 また、その動作確認をするプログラムを作成せよ。
int型、char型、double型などのいろいろな型を使って動作確認をすること。


文責:大津