ここで、与えられた引数の絶対値を返す関数を考えてみる。
例えば、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つの関数を比較して
みると、違いは引数と返り値の型のみである。
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行目がテンプレートを用いた関数の定義である。
「型をパラメータとして取り扱う」とはこのように型に依存しない形で汎用的
な記述を可能にするということである。
特定の型に依存しない汎用的な記述を目指したプログラミングを指して、
ジェネリックプログラミング (Generic Programming)と称することも
あるが、C++言語ではテンプレート機能を用いることで、それが可能となる。
上記の例では、型に依存しない形で関数を定義しているため、
汎用関数 (Generic Function)と呼ぶ。
また、関数以外にもクラスの定義を型に依存しない形で行なうことができ、
これを汎用クラス (Generic Class)と呼ぶ。
以下、それぞれについて説明する。
なお、後述する STL (Standard Template Library) は、
このテンプレートの機能をフルに活用して実装されている。
template <class Ttype>
ret_type func_name ( parameter_list )
{
// 関数の本体
}
テンプレート関数の呼び出しは以下のように記述する。
ただし、引数などから型が推測可能な場合はtype_name (型名)を省略 することが出来、通常の関数呼び出しと同じように記述できる。func_name<type_name> ( parameters );
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型かはコンパイラが自動的に判定して適切な関数を
生成してくれる。
実行結果は以下の通りになり、どちらの場合も同じ結果となる。
max(2,3) = 3 max(4,4.1) = 4.1
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