テンプレートの目的は、プログラマが一度コードを書くだけで、そのコードが型の形式に準拠して広範囲の型に適用できるようにすることです。この章では関数テンプレートに関連したテンプレートの概念と用語を紹介し、より複雑な (そして、より強力な) クラステンプレートと、テンプレートの使用方法について説明しています。また、テンプレートのインスタンス化、デフォルトのテンプレートパラメータ、およびテンプレートの特殊化についても説明しています。この章の最後には、テンプレートの潜在的な問題が挙げられています。
関数テンプレートは、引数または戻り値の型だけが異なった、関連する複数の関数を記述したものです。
テンプレートは使用する前に宣言する必要があります。次の例に見られるように、宣言によってテンプレートを使用するのに十分な情報が提供されますが、テンプレートを実装するにはほかの情報も必要です。
template <class Number> Number twice( Number original ); |
この例では Number はテンプレートパラメータであり、テンプレートが記述する関数の範囲を指定します。つまり、Number はテンプレート型のパラメータです。テンプレート定義内で使用すると、型はテンプレートを使用するときに特定されることになります。
テンプレートは宣言と定義の両方が必要になります。テンプレートを定義することで、実装に必要な情報が得られます。次の例は、前述の例で宣言されたテンプレートを定義しています。
template <class Number> Number twice( Number original ) { return original + original; } |
テンプレート定義は通常ヘッダーファイルで行われるので、テンプレート定義が複数のコンパイル単位で繰り返される可能性があります。しかし、すべての定義は同じである必要があります。この制限は「単一定義ルール」と呼ばれています。
テンプレートは、いったん宣言するとほかのすべての関数と同様に使用することができます。テンプレートの使用は、そのテンプレートの命名と関数引数の提供で構成されます。コンパイラは、テンプレート型引数を、関数引数の型から推測します。たとえば、以前に宣言されたテンプレートを次のように使用できます。
double twicedouble( double item ) { return twice( item ); } |
テンプレート引数が関数の引数型から推測できない場合、その関数が呼び出される場所にその引数を指定する必要があります。たとえば、次のようにします。
template<class T> T func(); // no function arguments int k = func<int>(); // template argument supplied explicitly |
クラステンプレートは、複数の関連するクラスまたはデータ型を記述します。クラステンプレートに記述されているクラスは、型、整数値、大域リンケージによる変数へのポインタや参照だけが互いに異なっています。クラステンプレートは、一般的ではあるけれども型が保証されているデータ構造を記述するのに特に便利です。
クラステンプレートの宣言では、クラスの名前とそのテンプレート引数だけを指定します。このような宣言は「不完全なクラステンプレート」と呼ばれます。
次の例は、任意の型の引数をとる Array というクラスに対するテンプレート宣言の例です。
template <class Elem> class Array; |
次のテンプレートは、unsigned int の引数をとる String というクラスに対する宣言です。
template <unsigned Size> class String; |
クラステンプレートの定義では、次の例のようにクラスデータと関数メンバーを宣言する必要があります。
template <class Elem> class Array { Elem* data; int size; public: Array( int sz ); int GetSize(); Elem& operator[]( int idx ); }; |
template <unsigned Size> class String { char data[Size]; static int overflows; public: String( char *initial ); int length(); }; |
関数テンプレートとは違って、クラステンプレートには class Elem のような型パラメータと unsigned Size のような式パラメータの両方を指定できます。式パラメータには次の情報を指定できます。
整数型または列挙型を持つ値
オブジェクトへのポインタまたは参照
関数へのポインタまたは参照
クラスメンバー関数へのポインタ
クラステンプレートを完全に定義するには、その関数メンバーと静的データメンバーを定義する必要があります。動的 (静的でない) データメンバーの定義は、クラステンプレート宣言で十分です。
テンプレート関数メンバーの定義は、テンプレートパラメータの指定と、それに続く関数定義から構成されます。関数識別子は、クラステンプレートのクラス名とそのテンプレートの引数で修飾されます。次の例は、template <class Elem> というテンプレートパラメータ指定を持つ Array クラステンプレートの 2 つの関数メンバー定義を示しています。それぞれの関数識別子は、テンプレートクラス名とテンプレート引数 Array<Elem> で修飾されています。
template <class Elem> Array<Elem>::Array( int sz ) {size = sz; data = new Elem[size];} template <class Elem> int Array<Elem>::GetSize() { return size; } |
次の例は、String クラステンプレートの関数メンバーの定義を示しています。
#include <string.h> template <unsigned Size> int String<Size>::length( ) {int len = 0; while (len < Size && data[len]!= ’\0’) len++; return len;} template <unsigned Size> String<Size>::String(char *initial) {strncpy(data, initial, Size); if (length( ) == Size) overflows++;} |
テンプレートの静的データメンバーの定義は、テンプレートパラメータの指定と、それに続く変数定義から構成されます。この場合、変数識別子は、クラステンプレート名とそのテンプレートの実引数で修飾されます。
template <unsigned Size> int String<Size>::overflows = 0; |
テンプレートクラスは、型が使用できる場所ならどこででも使用できます。テンプレートクラスを指定するには、テンプレート名と引数の値を設定します。次の宣言例では、Array テンプレートに基づいた変数 int_array を作成します。この変数のクラス宣言とその一連のメソッドは、Elem が int に置き換わっている点以外は、Array テンプレートとまったく同じです (「6.3 テンプレートのインスタンス化」を参照)。
Array<int> int_array(100); |
次の宣言例は、String テンプレートを使用して short_string 変数を作成します。
String<8> short_string("hello"); |
テンプレートクラスのメンバー関数は、ほかのすべてのメンバー関数と同じように使用できます。
int x = int_array.GetSize( ); |
int x = short_string.length( ); . |
テンプレートのインスタンス化には、特定の組み合わせのテンプレート引数に対応した具体的なクラスまたは関数 (インスタンス) を生成することが含まれます。たとえば、コンパイラは Array<int> クラスと Array<double> に対応した別々のクラスを生成します。これらの新しいクラスの定義では、テンプレートクラスの定義の中のテンプレートパラメータがテンプレート引数に置き換えられます。前述の「クラステンプレート」の節に示す Array<int> の例では、Elem が表示されるたびに、コンパイラが int に置き換えられます。
テンプレート関数またはテンプレートクラスを使用すると、インスタンス化が必要になります。そのインスタンスがまだ存在していない場合には、コンパイラはテンプレート引数に対応したテンプレートを暗黙的にインスタンス化します。
コンパイラは、実際に使用されるテンプレート引数に対応したテンプレートだけを暗黙的にインスタンス化します。これは、テンプレートを持つライブラリの作成には適していない可能性があります。C++ には、次の例のように、テンプレートを明示的にインスタンス化するための手段が用意されています。
テンプレート関数を明示的にインスタンス化するには、template キーワードに続けて関数の宣言 (定義ではない) を行います。関数の宣言では関数識別子のあとにテンプレート引数を指定します。
template float twice<float>(float original); |
テンプレート引数は、コンパイラが推測できる場合は省略できます。
template int twice(int original); |
テンプレートクラスを明示的にインスタンス化するには、template キーワードに続けてクラスの宣言 (定義ではない) を行います。クラスの宣言ではクラス識別子のあとにテンプレート引数を指定します。
template class Array<char>; |
template class String<19>; |
クラスを明示的にインスタンス化すると、そのメンバーもすべてインスタンス化されます。
テンプレート関数メンバーを明示的にインスタンス化するには、template キーワードに続けて関数の宣言 (定義ではない) を行います。関数の宣言ではテンプレートクラスで修飾した関数識別子のあとにテンプレート引数を指定します。
template int Array<char>::GetSize(); |
template int String<19>::length(); |
テンプレートの静的データメンバーを明示的にインスタンス化するには、template キーワードに続けてメンバーの宣言 (定義ではない) を行います。メンバーの宣言では、テンプレートクラスで修飾したメンバー識別子のあとにテンプレート引数を指定します。
template int String<19>::overflows; |
テンプレートは、 入れ子にして使用できます。これは、標準 C++ ライブラリで行う場合のように、一般的なデータ構造に関する汎用関数を定義する場合に特に便利です。たとえば、テンプレート配列クラスに関して、テンプレートのソート関数を次のように宣言することができます。
template <class Elem> void sort(Array<Elem>); |
また、次のように定義することができます。
template <class Elem> void sort(Array<Elem> store) {int num_elems = store.GetSize(); for (int i = 0; i < num_elems-1; i++) for (int j = i+1; j < num_elems; j++) if (store[j-1] > store[j]) {Elem temp = store[j]; store[j] = store[j-1]; store[j-1] = temp;}} |
前述の例は、事前に宣言された Array クラステンプレートのオブジェクトに関するソート関数を定義しています。次の例はソート関数の実際の使用例を示しています。
Array<int> int_array(100); // construct an array of ints sort(int_array); // sort it |
クラステンプレートのテンプレートパラメータには、デフォルトの値を指定できます (関数テンプレートは不可)。
template <class Elem = int> class Array; template <unsigned Size = 100> class String; |
テンプレートパラメータにデフォルト値を指定する場合、それに続くパラメータもすべてデフォルト値である必要があります。テンプレートパラメータに指定できるデフォルト値は 1 つです。
次の twice の例のように、テンプレート引数を例外的に特定の形式で組み合わせると、パフォーマンスが大幅に改善されることがあります。あるいは、次の sort の例のように、テンプレート記述がある引数の組み合わせに対して適用できないこともあります。テンプレートの特殊化によって、実際のテンプレート引数の特定の組み合わせに対して代替実装を定義することが可能になります。テンプレートの特殊化はデフォルトのインスタンス化を無効にします。
前述のようなテンプレート引数の組み合わせを使用するには、その前に特殊化を宣言する必要があります。次の例は twice と sort の特殊化された実装を宣言しています。
template <> unsigned twice<unsigned>( unsigned original ); |
template <> sort<char*>(Array<char*> store); |
コンパイラがテンプレート引数を明確に確認できる場合には、次の例のようにテンプレート引数を省略することができます。たとえば、次のようにします。
template <> unsigned twice(unsigned original); |
template <> sort(Array<char*> store); |
宣言するテンプレートの特殊化はすべて定義する必要があります。次の例は、前の節で宣言された関数を定義しています。
template <> unsigned twice<unsigned>(unsigned original) {return original << 1;} |
#include <string.h> template <> void sort<char*>(Array<char*> store) {int num_elems = store.GetSize(); for (int i = 0; i < num_elems-1; i++) for (int j = i+1; j < num_elems; j++) if (strcmp(store[j-1], store[j]) > 0) {char *temp = store[j]; store[j] = store[j-1]; store[j-1] = temp;}} |
特殊化されたテンプレートはほかのすべてのテンプレートと同様に使用され、インスタンス化されます。ただし、完全に特殊化されたテンプレートの定義はインスタンス化でもあります。
前述の例では、テンプレートは完全に特殊化されています。つまり、このようなテンプレートは特定のテンプレート引数に対する実装を定義しています。テンプレートは部分的に特殊化することも可能です。これは、テンプレートパラメータの一部だけを指定する、または、1 つまたは複数のパラメータを特定のカテゴリの型に制限することを意味します。部分特殊化の結果、それ自身はまだテンプレートのままです。たとえば、次のコード例に、本来のテンプレートとそのテンプレートの完全特殊化を示します。
template<class T, class U> class A {...}; //primary template template<> class A<int, double> {...}; //specialization |
次のコード例に、本来のテンプレートの部分特殊化を示します。
template<class U> class A<int> {...}; // Example 1 template<class T, class U> class A<T*> {...}; // Example 2 template<class T> class A<T**, char> {...}; // Example 3 |
例 1 は、最初のテンプレートパラメータが int 型である特殊なテンプレート定義です。
例 2 は、最初のテンプレートパラメータが任意のポインタ型である、特殊なテンプレート定義です。
例 3 は、最初のテンプレートパラメータが任意の型のポインタへのポインタであり、2 番目のテンプレートパラメータが char 型である、特殊なテンプレート定義です。
この節では、テンプレートを使用する場合の問題について説明しています。
テンプレート定義で使用される名前の中には、テンプレート引数によって、またはそのテンプレート内で、定義されていないものがある可能性があります。そのような場合にはコンパイラが、定義の時点で、またはインスタンス化の時点で、テンプレートを取り囲むスコープから名前を解決します。1 つの名前が複数の場所で異なる意味を持つために解決の形式が異なることも考えられます。
名前の解決は複雑です。したがって、汎用性の高い標準的な環境で提供されているもの以外は、非局所型名前に依存することは避ける必要があります。言い換えれば、どこでも同じように宣言され、定義されている非局所型名前だけを使用するようにしてください。この例では、テンプレート関数の converter が、非局所型名前である intermediary と temporary を使用しています。これらの名前は use1.cc と use2.cc では異なる定義を持っているため、コンパイラが異なれば結果は違うものになるでしょう。テンプレートが正しく機能するためには、すべての非局所型名前 (intermediary と temporary) がどこでも同じ定義を持つ必要があります。
use_common.h // Common template definition template <class Source, class Target> Target converter(Source source) {temporary = (intermediary)source; return (Target)temporary;} use1.cc typedef int intermediary; int temporary; #include "use_common.h" use2.cc typedef double intermediary; unsigned int temporary; #include "use_common.h" |
非局所型名前を使用する典型的な例として、1 つのテンプレート内で cin と cout のストリームの使用があります。ほとんどのプログラマは実際、ストリームをテンプレートパラメータとして渡すことは望まないので、1 つの大域変数を参照するようにします。しかし、cin と cout はどこでも同じ定義を持っている必要があります。
テンプレートインスタンス化の際には、型と名前が一致することを目安に、どのテンプレートがインスタンス化または再インスタンス化される必要があるか決定されます。したがって、局所型がテンプレート引数として使用された場合には重大な問題が発生する可能性があります。自分のコードに同様の問題が生じないように注意してください。たとえば、次のようにします。
array.h template <class Type> class Array { Type* data; int size; public: Array(int sz); int GetSize(); }; array.cc template <class Type> Array<Type>::Array(int sz) {size = sz; data = new Type[size];} template <class Type> int Array<Type>::GetSize() {return size;} file1.cc #include "array.h" struct Foo {int data;}; Array<Foo> File1Data(10); file2.cc #include "array.h" struct Foo {double data;}; Array<Foo> File2Data(20); |
file1.cc に登録された Foo 型は、file2.cc に登録された Foo 型と同じではありません。局所型をこのように使用すると、エラーと予期しない結果が発生することがあります。
テンプレートは、使用前に宣言されている必要があります。フレンド宣言では、テンプレートを宣言するのではなく、テンプレートの使用を宣言します。フレンド宣言の前に、実際のテンプレートが宣言されている必要があります。次の例では、作成済みオブジェクトファイルをリンクしようとするときに、operator<< 関数が未定義であるというエラーが生成されます。その結果、operator<< 関数はインスタンス化されません。
array.h // generates undefined error for the operator<< function #ifndef ARRAY_H #define ARRAY_H #include <iosfwd> template<class T> class array { int size; public: array(); friend std::ostream& operator<<(std::ostream&, const array<T>&); }; #endif array.cc #include <stdlib.h> #include <iostream> template<class T> array<T>::array() {size = 1024;} template<class T> std::ostream& operator<<(std::ostream& out, const array<T>& rhs) {return out <<’[’ << rhs.size <<’]’;} main.cc #include <iostream> #include "array.h" int main() { std::cout << "creating an array of int... " << std::flush; array<int> foo; std::cout << "done\n"; std::cout << foo << std::endl; return 0; } |
コンパイラは、次の宣言を array クラスの friend である正規関数の宣言として読み取っているので、コンパイル中にエラーメッセージを表示しません。
friend ostream& operator<<(ostream&, const array<T>&); |
operator<< は実際にはテンプレート関数であるため、template class array を宣言する前にこの関数にテンプレート宣言を行う必要があります。しかし、operator<< はパラメータ type array<T> を持つため、関数宣言の前に array<T> を宣言する必要があります。ファイル array.h は、次のようになります。
#ifndef ARRAY_H #define ARRAY_H #include <iosfwd> // the next two lines declare operator<< as a template function template<class T> class array; template<class T> std::ostream& operator<<(std::ostream&, const array<T>&); template<class T> class array { int size; public: array(); friend std::ostream& operator<< <T> (std::ostream&, const array<T>&); }; #endif |
C++ 標準は、テンプレート引数に依存する修飾名を持つ型を、typename キーワードを使用して型名として明示的に示すことを規定しています。これは、それが型であることをコンパイラが認識できる場合も同様です。次の例の各コメントは、それぞれの修飾名が typename キーワードを必要とするかどうかを示しています。
struct simple { typedef int a_type; static int a_datum; }; int simple::a_datum = 0; // not a type template <class T> struct parametric { typedef T a_type; static T a_datum; }; template <class T> T parametric<T>::a_datum = 0; // not a type template <class T> struct example { static typename T::a_type variable1; // dependent static typename parametric<T>::a_type variable2; // dependent static simple::a_type variable3; // not dependent }; template <class T> typename T::a_type // dependent example<T>::variable1 = 0; // not a type template <class T> typename parametric<T>::a_type // dependent example<T>::variable2 = 0; // not a type template <class T> simple::a_type // not dependent example<T>::variable3 = 0; // not a type |
「>>」 という文字連続型は右シフト演算子と解釈されるため、あるテンプレート名を別のテンプレート名で使用する場合は注意が必要です。隣接する「>」文字を少なくとも 1 つの空白文字で区切ってください。
次に誤った書式の例を示します。
Array<String<10>> short_string_array(100); // >> = right-shift |
前述の文は、次のように解釈されます。
Array<String<10 >> short_string_array(100); |
正しい構文は次のとおりです。
Array<String<10> > short_string_array(100); |
テンプレート定義の内部では、大域スコープや名前空間で静的として宣言されたオブジェクトや関数の参照がサポートされません。複数のインスタンスが生成されると、それぞれのインスタンスが別々のオブジェクトを参照するため、一定義規約 (C++ 標準の第 3.2 節) に違反するためです。通常、このエラーはリンク時にシンボルの不足の形で通知されます。
すべてのテンプレートのインスタンス化で同じオブジェクトを共有する場合は、そのオブジェクトを該当する名前空間の非静的メンバーにします。また、あるテンプレートクラスをインスタンス化するたびに、別々のオブジェクトを使用する場合は、そのオブジェクトを該当するテンプレートクラスの静的メンバーにします。同様に、あるテンプレート関数をインスタンス化するたびに、別々のオブジェクトを使用する場合は、そのオブジェクトを該当するテンプレート関数の局所メンバーにします。
-instances=extern を指定して複数のプログラムまたはライブラリを構築する場合は、それらを別のディレクトリに構築することを推奨します。同一ディレクトリ内に構築する場合は、構築ごとにリポジトリを消去する必要があります。これにより、予期しないエラーが回避されます。詳細は、「7.4.4 テンプレートリポジトリの共有」を参照してください。
次のような各ファイルが存在する、次の例を考慮してください。make ファイル、 a.cc、b.cc、x.h、および x.cc この例は、-instances=extern を指定する場合にのみ意味があることに注意してください。
........ Makefile ........ CCC = CC all: a b a: $(CCC) -I. -instances=extern -c a.cc $(CCC) -instances=extern -o a a.o b: $(CCC) -I. -instances=extern -c b.cc $(CCC) -instances=extern -o b b.o clean: /bin/rm -rf SunWS_cache *.o a b |
... x.h ... template <class T> class X { public: int open(); int create(); static int variable; }; |
... x.cc ... template <class T> int X<T>::create() { return variable; } template <class T> int X<T>::open() { return variable; } template <class T> int X<T>::variable = 1; |
... a.cc ... #include "x.h" int main() { X<int> temp1; temp1.open(); temp1.create(); } |
... b.cc ... #include "x.h" int main() { X<int> temp1; temp1.create(); } |
a と b の両方を構築する場合は、それらの構築の間に make clean を実行します。次のコマンドでは、エラーが発生します。
example% make a example% make b |
次のコマンドでは、エラーは発生しません。
example% make a example% make clean example% make b |