設計方法の1つに,ジェネリックプログラミングというものがある.これは,特定のコンセプトに依存しない一般的な概念を表現することで,汎用的なモジュールを生成する.例えば,C++では変数はある型の値を保持する存在であるが,その概念は型の種類に依存しない.変数はあくまで何かを入れるための器であって,型はその中身の種類やサイズを決定しているだけである.C++では,このような一般的な概念をテンプレートという仕組みによって表現できる.テンプレートでは,型を引数(型引数)として受け取ることによって,コンセプトに依存しない一般的な概念を表現できる.まず,変数を表現した例を示す(実用性はまるでないが).
template<class type> class Variable
{
public:
type val;
show(){ std::cout << val << std::endl; }
Variable(){}
~Variable(){}
};
int main()
{
Variable<int> i;
i.val = 10;
i.show();
Variable<double> d;
d.val = 3.141592;
d.show();
return 0;
}
テンプレートの定義
テンプレートを定義するための正しい文法は,次の通りである.
- template-declaration
- exportopt template <template-argument-list > declaration
- template-argument-list
- template-argument
- template-argument-list , template-argument
- template-argument
- type-argument
- argument-declaration
- type-argument
- class identifieropt
- class identifieropt = type-id
- typename identifieropt
- typename identifieropt = type-id
- template <template-argument-list > class identifieropt
- template <template-argument-list > class identifieropt = template-name
テンプレートのインスタンスの生成
テンプレートクラスとテンプレート引数を用いた宣言やテンプレート関数とテンプレート引数を用いた宣言は,テンプレートのインスタンス生成(template instantiation)と呼ばれる.テンプレートのインスタンスを生成する場合,宣言子(declarator) や抽象宣言子(abstract-declarator)内のid-expressionにtemplate-idを使用する.
- explicit-instantiation
- template declaration
- explicit-instatiation
- template < > declaration
- template-id
- template-name <template-argument-listopt >
- template-arguemnt-list
- template-argument
- template-argument-list , template-argument
- template-name
- identifier.
- type-argument
- class identifieropt
- class identifieropt = type-id
- typename identifieropt
- typename identifieropt = type-id
- template <template-argument-list > class identifieropt
- template <template-argument-list > class identifieropt = template-name
- 補足
- type-id は型や型修飾子からなる列.
- identifier は識別子(名前).
- クラステンプレートの場合
template <class T> class Var { public: T val; T operator=( T v ){ val = v; return v; } T show(){ std::cout << val << std::endl; return val; } }; int main() { Var<int> i; Var<char> c; i = 10; c = 'a'; i.show(); c.show(); return 0; }
- 関数テンプレートの場合
template<class T> T increment(T arg) { return arg++; } int main() { std::cout << increment<int>(11) << std::endl;//12 std::cout << increment<char>('a') << std::endl;//b return 0; }
テンプレート引数
テンプレートは,次のものをテンプレート引数として受け取ることができる.
- 型
- 通常のオブジェクト
- テンプレート
- 定数式
- 外部リンケージを持つオブジェクト
- 関数のアドレス
- メンバに対する多重定義されていないポインタ
template <class T, T v> class Hoge { };
template <class T, unsigned int size> class Hoge
{
public:
T vals[size];
};
int main()
{
Hoge<int, 10> ary1;
Hoge<char, 5> ary2;
ary1.vals[0] = 10;
ary2.vals[0] = 'a';
return 0;
}
テンプレートもまた関数と同じようデフォルト引数を持つ.これをデフォルトテンプレート引数と呼ぶ.テンプレート引数にオブジェクトやテンプレート引数を指定した場合,関数のデフォルト引数と同じである.もし型を指定した場合,そのデフォルト引数として型を指定できる.
テンプレートの特殊化
特定のテンプレート引数を与えられた時に,特殊な振る舞いを設定できる.このためのテンプレートの代替定義をユーザ定義の特別バージョンやテンプレートの特殊化と呼ぶ.
template <class T> class Hoge
{
public:
Hoge(){ std::cout << "etc\n"; }
};
template <> class Hoge<int>
{
public:
Hoge(){ std::cout << "int\n"; }
};
int main()
{
Hoge<char> c; // etc
Hoge<double> d; // etc
Hoge<int> i; // int
return 0;
}
汎用テンプレート
特殊化されてないテンプレートを汎用テンプレートと呼ぶ.汎用テンプレートは,特別バージョンよりも前に宣言か定義されてなければならない.宣言の場合,どこかで汎用テンプレートが定義されている必要がある.
テンプレートの部分特殊化
テンプレートの特殊化では,型の一部を特定するような代替定義を行うことができる.これらの代替定義をテンプレートの部分特別バージョンやテンプレートの部分特殊化と呼ぶ.
template <class T> class Hoge<T*>
{
public:
Hoge(){ std::cout << "pointer\n"; }
};
int main()
{
Hoge<char> c; // etc
Hoge<char*> cp; // pointer
Hoge<double> d; // etc
Hoge<int*> i; // pointer
return 0;
}
特別バージョンの優先順位
ある特別パターンに一致する全ての引数リストが,もう1つの特別パターンに一致するのに,逆は成り立たない場合,後者は前者より専門的である,という.例えば,特別バージョンのテンプレートは,汎用テンプレートよりも専門的である.コンパイラが曖昧性を解消する際,より専門的なテンプレートが選ばれる.
関数テンプレート
関数テンプレートの定義例を以下に示す.
template<class T1, class T2> T1 func(T2 t){ return u; }
関数テンプレートの引数
関数テンプレートの引数は,省略可能であるが,言語処理系が曖昧性を解消できない場合や適切な処理を行えない場合にエラーとなる.
int i = 10;
func(i); // エラー:曖昧
func<int>(i); // OK: 返り値を特定できる
func<int, int>(i); // OK: 返り値を明示的に指定
func<int*, int>(i); // エラー: intからint*への変換が無効
関数テンプレートの多重定義
C++では,同じ名前を持つ関数テンプレートや関数を定義できる.以下にそれらの曖昧性の解消に関するルールを示す.
- 基本的な規則
- そのスコープに属する関数テンプレートと関数が候補
- 関数テンプレートの引数には,格上げ,標準変換,ユーザ定義変換を適用することはできない.
- 曖昧性の解消
- スコープによって選ばれた関数テンプレートの中から最も専門的なものが選ばれる.
- 1.に通常の関数を加えた候補の中から,関数の多重定義の曖昧性を解消する規則に従って候補を絞る.
- 関数と関数テンプレートの一致の度合いが同じなら,関数が優先される.
- 一致するものがないか,複数ある時はエラーとなる.
派生とテンプレート
P406- 13.6 継承
テンプレートの変換
テンプレート引数としてのテンプレート
イテレータ(反復子)
イテレータとは,データ集合とアルゴリズムを結びつけるもののことである.データ集合に対するいくつかのアルゴリズムは,汎用的であり,データの性質に依存しないしかしイテレータのない言語では,プログラマは,データ構造にアクセスするためのインタフェースを用意し,そのインタフェースに従ったアルゴリズムを記述しなければならない.C++では,イテレータはデータ集合に対して標準的なアクセスモデルを提供する.そのためプログラマはそれにアクセスするための演算子や関数を特別に用意する必要はない.そしてプログラマに対し,データ構造に関係なくアルゴリズムを書けるようにする.
入力 | 出力 | 前方 | 双方向 | ランダムアクセス | |
---|---|---|---|---|---|
読み込み | =*p | =*p | =*p | =*p | |
アクセス | -> | -> | -> | ->[] | |
書き込み | *p= | *p= | *p= | *p= | |
反復 | ++ | ++ | ++ | ++ | ++ |
-- | |||||
+ | |||||
-- | - | ||||
+= | |||||
-= | |||||
比較 | == | == | == | == | == |
!= | |||||
< | |||||
!= | != | != | != | > | |
>= | |||||
<= |
export
exportはC++テンプレートの定義と宣言を分離するのを許可するよう指示する.ただしいくつかのコンパイラ(例えばgcc3.3以前やVisual C++など)は,exportがC++の正式な仕様であるにも関わらずサポートしていない.また,C++11ではexportは廃止となった。
その他
クラステンプレート名を多重定義することはできない.
template<class C> Hoge {};
class Hoge {}; //NG