C++ プログラミングガイド ホーム目次前ページへ次ページへ索引


第 4 章

テンプレート

テンプレートの目的は、プログラマが一度コードを書くだけで、そのコードが型の形式に準拠して広範囲の型に適用できるようにすることです。この章では関数テンプレートに関連したテンプレートの概念と用語を紹介し、より複雑な (そして、より強力な) クラステンプレートと、テンプレートの使用方法について説明しています。また、テンプレートのインスタンス化、デフォルトのテンプレートパラメータ、およびテンプレートの特殊化についても説明しています。この章の最後には、テンプレートの潜在的な問題が挙げられています。

関数テンプレート

関数テンプレートは、引数または戻り値の型だけが異なった、関連する複数の関数を記述したものです。

関数テンプレートの宣言

テンプレートは使用する前に宣言しなければなりません。次の例に見られるように、
「宣言」によってテンプレートを使用するのに十分な情報は与えられますが、テンプレートの実装には他の情報も必要です。

template <class Number> Number twice( Number original );

この例では Number は「テンプレートパラメータ」であり、テンプレートが記述する関数の範囲を指定します。つまり、Number は「テンプレート型のパラメータ」です。テンプレート定義内で使用すると、型はテンプレートを使用するときに特定されることになります。

関数テンプレートの定義

テンプレートは宣言と定義の両方が必要になります。テンプレートを「定義」することで実装に必要な情報が得られます。次の例は、前述の例で宣言されたテンプレートを定義しています。

template <class Number> Number twice( Number original )
    { return original + original; }

テンプレート定義は通常ヘッダーファイルで行われるので、テンプレート定義が複数のコンパイル単位で繰り返される可能性があります。しかし、すべての定義は同じでなければなりません。この制限は「単一定義ルール」と呼ばれます。

Sun WorkShop 6 C++ は、関数パラメータリスト内にテンプレートの型名でないパラメータを含む式をサポートしていません。次に例を示します。

// 関数パラメータリスト中にテンプレートの型名でない
// パラメータを含む式は、サポートされません。
template<int I> void foo( mytype<2*I> ) { ... }
template<int I, int J> void foo( int a[I+J] ) { ... }

関数テンプレートの使用

テンプレートは、いったん宣言すると他のすべての関数と同様に使用することができます。テンプレートを「使用」するには、そのテンプレートの名前とテンプレート引数を指定します。コンパイラは、テンプレート型引数を、関数引数の型から推測します。たとえば、以前に宣言されたテンプレートを次のように使用できます。

double twicedouble( double item )
    { return twice( item ); }

テンプレート引数が関数の引数型から推測できない場合、その関数が呼び出される場所にその引数を指定する必要があります。次に例を示します。

template<class T> T func(); // 関数引数なし
int k = func<int>(); // テンプレート引数を明示的に指定

クラステンプレート

クラステンプレートは、複数の関連するクラス (データ型) を記述します。グラステンプレートに記述されているクラスは、型のほかに整数値、または大域リンケージによる変数へのポインタや参照だけが互いに異なっています。クラステンプレートは、一般的ではあるけれども型が保証されているデータ構造を記述するのに特に便利です。

クラステンプレートの宣言

クラステンプレートの宣言では、クラスの名前とそのテンプレート引数だけを指定します。このような宣言は「不完全なクラステンプレート」と呼ばれます。

次の例は、任意の型の引数をとる 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 *inital )
    { strncpy( data, initial, Size );
      if ( length( ) == Size ) overflows++; }

静的データメンバーの定義

テンプレートの静的データメンバーの定義は、テンプレートパラメータの指定と、それに続く変数定義から構成されます。この場合、変数識別子は、クラステンプレート名とそのテンプレートの実引数で修飾されます。

template <unsigned Size> int String<Size>::overflows = 0;

クラステンプレートの使用

テンプレートクラスは、型が使用できる場所ならどこででも使用できます。テンプレートクラスを指定するには、テンプレート名と引数の値を設定します。次の宣言例では、Array テンプレートに基づいた変数 int_array を作成します。この変数のクラス宣言とその一連のメソッドは、Elemint に置き換わっている点以外は、Array テンプレートとまったく同じです (「テンプレートのインスタンス化」を参照)。

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> の例では、すべての Elemint に置き換えられます。

テンプレートの暗黙的インタンス化

テンプレート関数またはテンプレートクラスを使用すると、インスタンス化が必要になります。そのインスタンスがまだ存在していない場合には、コンパイラはテンプレート引数に対応したテンプレートを暗黙的にインスタンス化します。

全クラスインスタンス化

コンパイラは、あるテンプレートクラスを暗黙的にインスタンス化するとき、使用されるメンバーだけをインスタンス化します。コンパイラがあるクラスを暗黙的にインスタンス化するときにすべてのメンバー関数をインスタンス化するには、コンパイラオプションの -template=wholeclass を使用します。このオプションを無効にするには、
-template=no%wholeclass を指定します。

テンプレートの明示的インスタンス化

コンパイラは、実際に使用されるテンプレート引数に対応したテンプレートだけを暗黙的にインスタンス化します。これは、テンプレートを持つライブラリの作成には適していない可能性があります。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>::overflow;

テンプレートの編成

テンプレートは、入れ子にして使用できます。これは、標準 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 );   // intの配列を作成し、
sort( int_array );        // それをソートする。

デフォルトのテンプレートパラメータ

クラステンプレートのテンプレートパラメータには、デフォルトの値を指定できます (関数テンプレートは不可)。

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 { ... }; //本来のテンプレート
template<> class A<int, double> { ... };    //特殊化

次のコード例に、本来のテンプレートの部分特殊化を示します。

template<classU> class A<int> { ... };          // 例 1
template<class T, class U> class A<T*> { ... }; // 例 2
template<class T> class A<T**, char> { ... };   // 例 3

テンプレートの問題

この節では、テンプレートを使用する場合の問題について説明しています。

非局所型名前の解決とインスタンス化

テンプレート定義で使用される名前の中には、テンプレート引数によって、またはそのテンプレート内で、定義されていないものがある可能性があります。そのような場合にはコンパイラが、定義の時点で、またはインスタンス化の時点で、テンプレートを取り囲むスコープから名前を解決します。1 つの名前が複数の場所で異なる意味を持つために解決の形式が異なることも考えられます。

名前の解決は複雑です。したがって、汎用性の高い標準的な環境で提供されているもの以外は、非局所型名前に依存することは避ける必要があります。言い換えれば、どこでも同じように宣言され、定義されている非局所型名前だけを使用するようにしてください。この例では、テンプレート関数の converter が、非局所型名前である intermediary と temporary を使用しています。これらの名前は use1.ccuse2.cc では異なる定義を持っているため、コンパイラが異なれば結果は違うものになるでしょう。テンプレートが正しく機能するためには、すべての非局所型名前 (intermediary と temporary) がどこでも同じ定義を持つ必要があります。

use_common.h
// 共通のテンプレート定義
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 はどこでも同じ定義を持っていなければなりません。

テンプレート引数としての局所型

テンプレートインスタンス化の際には、型と名前が一致することを目安に、どのテンプレートがインスタンス化または再インスタンス化される必要があるか決定されます。したがって、局所型がテンプレート引数として使用された場合には重大な問題が発生する可能性があります。自分のコードに同様の問題が生じないように注意してください。次に例を示します。

コード例\x11 4-1   テンプレート引数としての局所型の問題の例
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;
file2.cc
#include "array.h"
struct Foo { double data; };
Array<Foo> File2Data;


file1.cc の中に登録された Foo 型は、file2.cc の中に登録された Foo 型と同じではありません。局所型をこのように使用すると、エラーと予期しない結果が発生することがあります。

テンプレート関数のフレンド宣言

テンプレートは、使用前に宣言されていなければなりません。フレンド宣言では、テンプレートを宣言するのではなく、テンプレートの使用を宣言します。フレンド宣言の前に、実際のテンプレートが宣言されていなければなりません。次の例では、作成済みオブジェクトファイルをリンクしようとするときに、operator<< 関数が未定義であるというエラーが生成されます。その結果、operator<< 関数はインスタンス化されません。

コード例\x11 4-2   フレンド宣言の問題の例
array.h
// operator<< 関数に対して未定義エラーを生成する
#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>

 
// 次の 2 行は operator<< をテンプレート関数として宣言する
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<<(std::ostream&, const array<T>&);
};
#endif

テンプレート定義内での修飾名の使用

C++ 標準は、テンプレート引数に依存する修飾名を持つ型を、typename キーワードを使用して型名として明示的に示すことを規定しています。これは、それが型であることをコンパイラが認識できる場合も同様です。次の例の各コメントは、それぞれの修飾名が typename キーワードを必要とするかどうかを示しています。

struct simple {
    typedef int a_type;
    static int a_datum;
};
int simple::a_datum = 0; // 型ではない
template <class T> struct parametric {
    typedef T a_type;
    static T a_datum;
};
template <class T> T parametric<T>::a_datum = 0;      // 型ではない
template <class T> struct example {
    static typename T::a_type variable1;              // 必要
    static typename parametric<T>::a_type variable2;  // 必要
    static simple::a_type variable3;                  // 不要
};
template <class T> typename T::a_type                 // 必要
                   example<T>::variable1 = 0;         // 型ではない
template <class T> typename parametric<T>::a_type     // 必要
                   example<T>::variable2 = 0;         // 型ではない
template <class T> simple::a_type                     // 不要
                   example<T>::variable3 = 0;         // 型ではない
template class example<simple>

テンプレート宣言の入れ子

「>>」という文字を持つものは右シフト演算子と解釈されるため、あるテンプレート宣言を別のテンプレート宣言内で使用する場合は注意が必要です。隣接する「>」文字との間に、少なくとも 1 つの空白文字を入れるようにしてください。

以下に誤った書式の例を示します。

// 誤った書式の文
Array<String<10>> short_string_array(100); // >> は右シフトを示す。

上記の文は、次のように解釈されます。

Array<String<10  >> short_string_array(100);

正しい構文は次のとおりです。

Array<String<10> > short_string_array(100);


サン・マイクロシステムズ株式会社
Copyright information. All rights reserved.
ホーム   |   目次   |   前ページへ   |   次ページへ   |   索引