C++ プログラミングガイド

テンプレートの問題

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

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

テンプレート定義で使用される名前の中には、テンプレート引数によって、またはそのテンプレート内で、定義されていないものがある可能性があります。そのような場合にはコンパイラが、定義の時点で、またはインスタンス化の時点で、テンプレートを取り囲むスコープから名前を解決します。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 のストリームの使用があります。ほとんどのプログラマは実際、ストリームをテンプレートパラメータとして渡すことは望まないので、一つの大域変数を参照するようにします。しかし、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;

file2.cc      #include "array.h"
              struct Foo { double data; };
              Array<Foo> File2Data;

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

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

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


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);