C/C++ - 言語仕様編 第10回 〜テンプレート〜

テンプレート関数

宣言

int f(int n);

は、int 型変数 n を受け取り、int 型変数を返す。

C++ では、オーバーロードによって、この f(int n); 関数と、

double f(double v);
C f(C c);

などといった関数は区別されるが、もし、この3つの振る舞いが全く同じである場合、 すなわち、f(int n) や f(double n) で使用される演算子が、適切にオペレータ・オーバーロードされている 一般のクラス T に対しても動作させられるようにする場合、これを、

template<typename T> T f(T t);

として代表させる事ができる。この f をテンプレート関数という。また、T をテンプレート引数という。

呼び出し

テンプレート関数 f を使用する場合は、そのテンプレート引数 T に対して、型を指定し、

f<C>(t1);

などと型名を指定することで、テンプレート関数が使用できる。

このテンプレート関数 f<C>(C c1); のようにテンプレート引数 T が、引数の c1 から曖昧さなしに推測可能な場合、 テンプレート引数の明示的な指定を行うことを省略し、

f(c1);

としても動作する。

例えば、テンプレート関数、

template<typename S, typename T> S g(T t);

という関数がある場合、引数によってテンプレート引数 T は推測できるが、S は推測できない。 従って、テンプレート引数 S は常に明示的に、

g<C>(t1);

などと指示しなくてはならない。

インラインテンプレート関数

C 言語におけるマクロ関数、

#define MULTI(a,b) ((a)*(b))

を、テンプレート関数を用いて、

template inline const T& multi(const T& a, const T& b)
{
	return a*b;
}

などとすることで、書き換えることができる。

マクロ関数の場合に比べ、インラインテンプレート関数では、型を明示的に決定する事ができるといった利点が存在する。

クラスとテンプレート

テンプレートメソッド

当然、クラス C のメソッド m を、テンプレートメソッドとして、

class C{
public:
	template<typename T> T m(const T& t);
}

などと宣言し、

template<typename T> T C::m(const T& t)
{
	...
}

などと定義することが出来る。

クラス C のインスタンスを持つオブジェクト c に対して、

c.m(a1);

などとすれば、テンプレートメソッド m のテンプレート引数を int として実行する事が出来る。

同じ原理で、テンプレートコンストラクタ、

template<typename T> C(const T& t);

を宣言することもでき、テンプレートキャストオペレータ、

template<typename T> operator T(void);

などを宣言する事も可能である。

テンプレートクラス

テンプレート化は極端なところまで進み、クラス自身を、

template<typename T> class C{
	T p;
public:
	C(void);
	C(const T& t);
};

などとすることも可能である。この場合、クラス C のインスタンス化は、明示的にテンプレート引数を指定し、

C<int>* c=new C<int>;

のような指示を行う。

テンプレートクラス内でのメソッドの宣言時は、テンプレート引数 T を使用する際でも、

T m(const T& t);

などと、直接宣言することが出来るが、これを定義する際には、

template<typename T> T C::m(const T& t);

と書かなくてはならない。

テンプレートの特殊化

テンプレート関数の特殊化

テンプレート関数

template<typename T> T m(const T& t);

に対して、テンプレート引数 T が、A の場合に、その関数の挙動を変えたい場合には、

template<> A m(const A& a);

として、const A&型引数の場合にのみ適用される特殊化バージョンを作ることができる。

テンプレートクラスの部分特殊化

テンプレートクラス

template<typename S> class C{
	...
};

に対して、ある型 T 指定時の定義をする場合は、このような一般のテンプレートクラス定義後に、

template<> class C<T>{
	...
};

とすることで特殊化することができる。ここで、一般の定義におけるテンプレート引数の数は、 特殊化後のテンプレート引数の数とクラス名の後に指定する実際の型の数の和に等しくなくてはならない。

また、テンプレート引数 S に対して、例えば、そのポインタ型 S* の場合に特殊化する、

template<typename S> class C<S*>{
	...
};

という特殊化を、特に、テンプレートクラスの部分特殊化と呼ぶ。

通常の特殊化の場合は、テンプレート引数の指定がなく、template<>となるが、 部分特殊化の場合は、template<typename S>のように、そのテンプレート引数の指定が残る。

テンプレート引数

デフォルト引数

テンプレート引数にはそのデフォルト引数を指定することができる。例えば、

template<typename T=C> class T{
	...
};

などと指定することにより、例えば、

T<> t;

とオブジェクトを構築すると、これは、T<C>オブジェクトとなる。

型指定テンプレート引数

テンプレート引数は typename として指示するが、ここに具体的な型を指定し、

template<typename T, int N> class A{
	T t[N];
	...
};

というようなテンプレートクラスを構築することが出来る。

A<int,4>と A<int,5> は明確に区別される別の型のクラスである。

テンプレート関数の引数として指定する場合、テンプレート引数 S に対して、

template<typename S> void m1(S& s);

として、s に対して、A<C,4>型引数を指定することは当然出来るが、これを、

template<typename T,int N> void m2(A<T,N>& a);

とすることで、型 T と、要素数 N を取得することが出来るテンプレート関数がつくれる。

テンプレート関数の場合でそうであったように、曖昧さなしで推測できる場合、テンプレート引数 T, N は、 明示的に指示しなくても自動的に決定される。

配列テンプレート

template<typename T, int N> void m(T (&a)[N])
{
	...
}

などとすると、配列に対して、そのサイズを N として取得することができる。

これは、ある定数 n を用いて、T[n]として宣言したものであっても、 T[]として、初期化の段階でその要素数を定めたものであっても、同様に取得が可能であり、 さらに、配列でなければ引数とすることができない。つまり、ポインタ変数では、 たとえ、それが添字オペレータでアクセスできる配列をさすものであっても、引数として指定できない。

テンプレート全体に共通することではあるが、テンプレートは、コンパイル時に自動的にその定義が 各テンプレート引数ごとに作られる。

当然、型指定テンプレート引数の場合も、その実体がいくつも作られるため、 しばしば、実行ファイルサイズの肥大化という問題を引き起こす原因となる。

ファンクタとプレディケート

関数オペレータと関数オブジェクト

関数オペレータを持つようなテンプレートクラスをファンクタと呼ぶ。

template<typename T> class F{
public:
	T operator()(T t);
};

のように定義されたファンクタ F のオブジェクトを、関数オブジェクトという。

F<C> f;

として、構築した関数オブジェクト f は、C 型オブジェクト c を用いて、

f(c);

とすることで、関数オペレータが呼び出される。C 言語でいうところの関数ポインタのクラス化バージョンであると考えてよい。

関数オペレータと述語オブジェクト

関数オペレータが、

template<typename T> class P{
public:
	bool operator()(T t);
};

のように、bool 型を返すようなテンプレートクラスをプレディケートと呼び、 プレディケート P のオブジェクトを、述語オブジェクトという。

引数としての関数オブジェクト

1引数を要求するファンクタ F の関数オブジェクト f1 を、テンプレート関数

template<typename Func> void m(Func f)
{
	f(t1);
}

に代入すると、関数内で、Func は、ファンクタ F となり、f は、関数オブジェクトとして機能する。

1引数を要求する関数オペレータを持つファンクタならば、どんなファンクタでも使用できることから、 その意味で、このテンプレート関数は多態性を持つということ出来る。

イテレータ

インクリメント・デクリメントオペレータや、参照系オペレータなどが定義されたテンプレートクラス、

template<typename T> class I{
	T* itr;
public:
	I& operator++(void);
	I& operator--(void);
	T& operator*(void);
	T* operator->(void);
};

を、イテレータと呼ぶ。C 言語でいうところの反復子のクラス化バージョンであると考えてよい。

オペレータ・オーバーロードにより、インクリメントなどが再定義できることから、 配列のようなデータ構造に限らず、より一般の構造に対しても同様の操作性を実現することが出来る。

各々のデータ構造に特有の操作を、オペレータに代行させることによって、 カプセル化を実現し、また、共通する概念をオペレータで表現することで、多態性を実現する。


文章作成 : yukki-ts (-+-twilight serenade-+- [stage])