テンプレート (プログラミング)プログラミングにおけるテンプレートは、静的型付けのプログラミング言語でデータ型を抽象化してコードを書くことを可能にする機能であり、C++やD言語においてはジェネリックプログラミングに用いられる。 C++のテンプレートは後から追加された機能だが、多重継承や演算子多重定義と並ぶ重要な機能となった。STL (Standard Template Library) はテンプレートによって構築された汎用的なアルゴリズムやデータ構造を含むソフトウェアフレームワークとなっている。 C++概要C++11規格までのテンプレートには、大別して関数テンプレートとクラステンプレートがある。C++14では変数テンプレートもサポートするようになった。 関数テンプレートは任意の型の引数を受け取ることができる関数のようなものである。たとえば、標準C++ライブラリにある関数テンプレート template <typename T>
const T& max(const T& x, const T& y)
{
if (x < y)
return y;
else
return x;
}
そして次のように呼び出せる。 using namespace std;
cout << max<int>(3, 7) << endl; // 7が出力される。
cout << max(3, 7) << endl; // max(int, int) から max<int>(const int&, const int&) に推論される。
cout << max(3, 7L) << endl; // int, long の曖昧さが解決できないためコンパイルエラー。
cout << max<double>(-3.1, -8) << endl; // -3.1が出力される。
cout << max(-3.1, -8.0) << endl; // max(double, double) から max<double>(const double&, const double&) に推論される。
cout << max(-3.1, -8) << endl; // double, int の曖昧さが解決できないためコンパイルエラー。
ここで、テンプレート内にて
C++のテンプレートは完全にコンパイル時点でタイプセーフである。例えば、複素数には狭義の順序 (strict order) がないので、標準C++ライブラリの複素数型である 一般的に、そのようなテンプレート実引数に対する要求は、コンセプト (concept) や要件 (requirement) などとして別途文書によって記述されている。標準C++ライブラリの テンプレートに関するコンパイルエラーメッセージを改善するために、C++0xではコンセプトと呼ばれる機能の導入が検討されていたが、結局C++11では見送られた。コンセプトはC++17規格でも実装されていない。 クラステンプレートはテンプレートの概念をクラスに当てはめたものである。汎用的なコンテナ (generic containers) の作成によく用いられる。たとえばSTLにはリンクリストとして テンプレート引数の数はC++03までは固定であり、定義時にひとつひとつ明示的に指定する必要があったが、C++11では可変引数テンプレート (variadic template) がサポートされるようになった。可変長引数の関数やマクロのように、 テンプレートの明示的特殊化と部分特殊化→「テンプレートの部分特殊化」も参照
テンプレートの明示的特殊化 (explicit specialization) は、テンプレート引数について、特定の型に対する明示的個別実装を可能とする機能である。テンプレートの明示的特殊化は、特定の形式に最適化できるようにすることと、コード肥大化の削減に役立てることという2つの目的がある。 例として テンプレートの部分特殊化 (partial specialization) は、テンプレート仮引数の一部を特殊化するものである。明示的特殊化と異なる点は、特殊化されず抽象化されたままのテンプレート仮引数を持つことである。 テンプレートメタプログラミングC++のテンプレートはまた、実行時ではなくコンパイル時にコードの一部を事前評価する方法であるテンプレートメタプログラミングにも利用できる。C++のテンプレートはチューリング完全である。ただし、コンパイラによる制限がある。規格上は再帰の深さは参考 (informative) で処理系限界 (implementation limits / implementation quantities) であり、C++の作業原案 (working draft) のひとつ N3797 では1024を最小として例示している[2]。再帰の制限が無ければコンパイル処理が永遠に終了しないコードを記述することができ、それをコードから判断することはコンパイラには不可能だからである。停止性問題を参照。 利点と欠点テンプレートの用法の中にはC言語のプリプロセッサマクロで代替できるものがある。例えば #define MAX(a, b) ((a) < (b) ? (b) : (a))
マクロもテンプレートもコンパイル時までに展開される。マクロは常にインライン展開されるが、関数テンプレートはコンパイラの判断によってインライン関数として扱われる。そのため(関数テンプレートがインライン展開される限り)実行時のオーバーヘッドは両者共に発生しない。 しかしテンプレートはマクロに比べて型安全であるという点で大きな違いがある。関数形式のマクロでよく起こるエラーをテンプレートでは回避できる。関数テンプレートでは、関数形式マクロのように引数の意図しない多重評価は起こらない。そしてマクロのおそらく重大な欠点として1論理行に収めなければならないという制限があり、規模の大きいマクロは記述が面倒ということがある。テンプレートはマクロよりも大規模なコードに対して特に効果的である。また、マクロは常にグローバル名前空間を汚染するが、テンプレートは任意の名前空間あるいは構造体やクラスの内部に定義することができる。 一方、テンプレートには大きく三つの欠点がある。それはコンパイラのサポート、貧弱なエラーメッセージ、コードの肥大化である。 まず、歴史的に多くのコンパイラの対応が貧弱だったため、テンプレートを使うと移植性が低下する恐れがある。C++について考慮していないリンカを利用するとき、あるいは共有ライブラリの境界を越えてテンプレートを使おうとしたときも十分な対応がなされていない処理系があった。C++98以降はテンプレートを含めた規格標準化とコンパイラ対応が進み、改善されている。 次に、ほとんど全てのコンパイラは、テンプレートを使ったコードにエラーを検出したときに、混乱を誘発するような、長く、そして役に立たないエラーメッセージを出力する。これはテンプレートを使った開発を難しくしている。 最後に、テンプレートは実体化される度にコードが生成されていくため、無秩序に使っていくとコードの膨張を引き起こし実行ファイルが大きくなってしまう問題がある。しかしながら一部のケースでは、うまくテンプレートの特殊化を利用することで、そのようなコードの肥大化を劇的に減らすことができる。そのほか、実体化されたもののうち、完全に同じテンプレート実引数に対するコードは共有するように最適化されたコードを出力するコンパイラを利用することで改善できるが、これはインライン化によるオーバーヘッド低減とのトレードオフでもある。また、テンプレートによって生じる余分なインスタンス化はデバッガが素直にテンプレートを扱うことを困難にする原因ともなりうる。例えばソースコードのテンプレートの中にブレークポイントをセットする場合、実際のインスタンスの中の望ましい場所にセットすることに失敗するかもしれないし、そのテンプレートによってインスタンス化された全ての場所にブレークポイントを設置しなければならないかもしれない。 テンプレートは実体化に際して完全な定義が必要である。非テンプレートの関数やクラスのように、宣言部をヘッダーファイルに記述し、実装部をソースファイルに分離することはできない。C++03には 他言語C++を基にしたJavaやC#では、C++のテンプレートが引き起こした問題を避けるためテンプレートは除外された。しかし、後にテンプレートに似た機能(ジェネリクス; generics)を導入してジェネリックプログラミングに適応しようとしている。 D言語にもテンプレートは存在するが、C++のものより更に改良を加えられている。例えば、テンプレート引数として文字列や浮動小数点数、さらには変数や関数などのシンボルを渡すことができる。これにより、高階関数のテンプレート引数に文字列として渡された式をインライン化するといった手法が可能になっている。その他にも、テンプレート制約を指定できる組み込みの構文や、テンプレート引数によって実体化されるコードを変化させられるstatic if等の機能が用意されており、中にはD言語の開発メンバーなどによってC++への取り込みが提案されているものもある。 AdaはC++がテンプレートを採用する以前からジェネリクス(汎用体)を持っていたが、それはテンプレートの特殊化が行なえないなどC++のテンプレートに比して機能は制限されたものであり、C++のようなテンプレートメタプログラミングを行なうことはできなかった。 参考文献脚注関連項目 |