テンプレート関数とテンプレートクラス

テンプレートを使った関数とクラスについてのまとめです。

テンプレートが必要な理由

float型の要素を持つベクトルと、int型の要素をもつベクトルを作成することを考えてみます。ベクトル同士の演算は型に依存しない処理ですが、それぞれの型に対してfVector,iVectorなど専用のクラスを作成しなければなりません。floatとintの2つだけならまだしも、char,long,doubleなどターゲットにしたい型の数が多いと、処理が同じで型の情報のみ異なるクラスばかり増えてしまうことになります。
型に関する情報を変数のようにして後から指定することができれば、コードを一つにまとめることができます。C++ではテンプレート化という手法を用いるとコンパイル時に型を指定して、その型専用のクラスや関数を生成するという機能によってこれを実現することができます。

テンプレート関数

型が入る部分を「箱」だと考えて関数を定義します。型を全て[Type]に置き換えて考えます。
関数名も型に依存するので関数名にはType型専用という意味でを付加してみます。
(ちなみにこの関数をsquareと呼んでいいのか疑問)

テンプレート化前
//一つずつ定義
int iSquare(int x,int y){
	return x * x + y * y;
}	
float fSquare(float x,float y){
	return x * x + y * y;
}
//型を一般化した書式(注:正しい文法ではない) Type = int , float
Type square<Type>(Type x,Type y){
	return x * x + y * y;
}	

C++での正しい記述は次のようになります。

//テンプレート関数
template <class T>
T square(T x,T y){
	return x * x + y * y;
}	

型の名前を表す"T"は他の名前でも問題ありません。
型の情報が二種類の場合は「箱」が2つあると考えて関数を定義します。今度は戻り値型と引数型の2種類あるので、型を全て[Type1],[Type2]に置き換えて考えます。また、関数名は2つの型Type1,Type2に依存するので。という識別子を付加してみます。

テンプレート化前
//一つずつ定義
int iSquare(float x,float y)	{
	return int(x * x + y * y);	
}	
int iSquare(int x,int y)	{	
	return int(x * x + y * y);	
}
int iSquare(double x,double y)	{	
	return int(x * x + y * y);	
}
	
float fSquare(int x,int y)	{	
	return float(x * x + y * y);	
}
float fSquare(float x,float y)	{	
	return float(x * x + y * y);	
}
float fSquare(double x,double y){	
	return float(x * x + y * y);	
}
//型を一般化した関数 Type1 = int, float; Type2 =int, float, double 
Type1 square<Type1,Type2>(Type2 x,Type2 y){
	return Type1(x * x + y * y);
}

正しい文法に沿った書き方は次のようになります。

//テンプレート関数
template <class T, class U>
T square(U x,U y){
	return T(x * x + y * y);
}

使い方

テンプレート関数を使う場合は、どの型についての関数なのかをコンパイラに教えなければいけないので、関数名にターゲットにしたい型を指定します。例えば、int専用の関数を作る場合はを関数名につけて明示します。

int r1 = square<int>(2,4);
float r2 = square<float>(2.0f,4.2f);
double r3 = square<double>(1.01,-9.92);
2テンプレート引数の例
int r1 = square<int,float>(2.0f,4.0f);
float r2 = square<float,double>(1.0,4.2);
double r3 = square<double,int>(1,-10);

テンプレートクラス

関数と同様にクラスもテンプレート化することができます。考え方は同じで、型情報を「箱」だと考えます。
クラス名が型に依存しているという意味で、クラス名にを付けて型を抽出してみます。

class iVector{
	int x;
	int y;
};
class fVector{
	float x;
	float y;
};
//型を一般化した関数 Type = int , float
class Vector<Type>{
	Type x;
	Type y;
};
//テンプレートクラス
template <class T>
class Vector{
	T x;
	T y;
};

テンプレートメンバ関数

メンバ関数をテンプレート化することもできます。手順は普通の関数と同じです。

class iVector{
	int x;
	int y;	
	iVector prod(int k){
		x*=k; y*=k; return *this;
	}
	iVector prod(float k){
		x*=k; y*=k; return *this;
	}
	iVector prod(double k){
		x*=k; y*=k; return *this;
	}	
};
//型を一般化したメンバ関数 Type2 = int, float;
class iVector{
	int x;
	int y;
	iVector prod<Type2>(Type2 k){
		x*=k; y*=k; return *this;
	}
};
//テンプレートメンバ関数
class iVector{
	int x;
	int y;
	template <class U>
	iVector prod(U k){
		x*=k; y*=k; return *this;
	}
};
テンプレートメンバ関数を持つテンプレートクラス

先ほどと同様に、型の情報が2種類の場合は、別々に箱を用意します。例えばクラスのメンバ変数と、メンバ関数の定義で異なる型の場合です。クラスはType1に依存しているのでVectorとしておきます。

class iVector{
	int x;
	int y;	
	iVector prod(int k){
		x*=k; y*=k; return *this;
	}
	iVector prod(float k){
		x*=k; y*=k; return *this;
	}
	iVector prod(double k){
		x*=k; y*=k; return *this;
	}	
};
class fVector{
	float x;
	float y;	
	fVector prod(int k){
		x*=k; y*=k; return *this;
	}
	fVector prod(float k){
		x*=k; y*=k; return *this;
	}
	fVector prod(double k){
		x*=k; y*=k; return *this;
	}	
};
//型を一般化したクラス Type1 = int, float;
class Vector<Type1>{
	Type1 x;
	Type1 y;
	Vector<Type1> prod(int k){
		x*=k; y*=k; return *this;
	}
	Vector<Type1> prod(float k){
		x*=k; y*=k; return *this;
	}
	Vector<Type1> prod(double k){
		x*=k; y*=k; return *this;
	}
};
//クラスのテンプレート化
template <class T>
class Vector{
	T x;
	T y;
	Vector prod(int k){
		x*=k; y*=k; return *this;
	}
	Vector prod(float k){
		x*=k; y*=k; return *this;
	}
	Vector prod(double k){
		x*=k; y*=k; return *this;
	}
};

メンバ関数も処理が同じなのでテンプレート化できます。これらは全てVectorを戻り値として、Type2=int,float,doubleを引数とする関数です。引数の方だけ箱化したのでメンバ関数はType2にのみ依存します。

//さらに型を一般化したメンバ関数 Type2 =int, float, double 
template <class T>
class Vector{
	T x;
	T y;
	Vector prod<Type2>(Type2 k){
		x*=k; y*=k; return *this;
	}
};
//メンバ関数もテンプレート化
template <class T>
class Vector{
	T x;
	T y;
	template <class U>
	Vector prod(U k){
		x*=k; y*=k; return *this;
	}
};

使い方

関数のときと同様に、テンプレート化したクラスとメンバ関数の両方に対してターゲットとなる型を指定します。クラスがfloat専用、prodがint専用ならVector, prodと記述します。

Vector<float> v;
v.x=0.1f;  v.y=1.2f;
v.prod<int>(3);

最後に注釈:
などの識別子を省略することができる場合がある
特定の型のみ別の処理を行うなどの機能を実現することもできる