|
本サイトは移転しました。旧アドレスからのリダイレクトは2025年03月31日(月)まで有効です。
|
🛈 | ✖ |
C++でデータ型を抽象化してコードを書くことを可能にする機能であり、総称プログラミングなどに用いられる。
テンプレートはクラスあるいは関数の群を定義する。その要素となるクラス/関数はテンプレートの仮引数(型や整数定数など)に実引数を与えてコンパイル時に生成する。C++の黎明期における重要な拡張の一つだが(Bjarne Stroustrup, The Design and Evolution of C++, Reading, Addison-Wesley, 1994; Reading, Addison-Wesley, 1998, pp.337-338)、最初の規格書であるARM(Margaret A. Ellis et al., The Annotated C++ Reference Manual, Reading, Addison-Wesley, 1990; Reading, Addison-Wesley, 1997)は定義にわずか10ページのみを割く(ARM, pp.341-351)。しかしC++17で80ページまで膨張し(JTC1/SC22/WG21 N4659 17)、これは見込みよりはるかに複雑な問題をテンプレートがもたらした結果とされる。つまりテンプレートの外面は単純な便利機能だが背後に単純でない危険性を隠し持つ。
クラス群を定義するテンプレートをクラステンプレート、関数群を定義するテンプレートを関数テンプレートと呼ぶ。C++17では変数群を定義するテンプレートと型群への別名を定義するテンプレートが追加されているが(N4659 17/p1)、本サイトは断らない限りクラステンプレートと関数テンプレートの総称としてテンプレートを参照する。テンプレートの定義する物の変遷をまとめておく。
規格 | テンプレートの定義するもの |
---|---|
ARM, C++98, C++03 | クラス群、関数群 |
C++11, C++14 | クラス群、関数群、型群への別名 |
C++17 | クラス群、関数群、変数群、型群への別名 |
C++20, C++23 | クラス群、関数群、変数群、型群への別名、コンセプト |
教科書(Stephen C. Dewhurst, C++ Common Knowledge, Upper Saddle River, Addison-Wesley, 2005, pp.153-154)によるテンプレート用語に従う。
より広範囲かつ正確には別教科書(David Vandevoorde et al., C++ Templates, Boston, Addison-Wesley, 2003, pp.507-516)に詳しく、これとC++規格(N4659)をベースに本記事が使用する用語を補足する。これらは説明を目的として大きく簡略化して規格に照らせば全く厳密でない。
用語 | N4659 | 説明 | 構成 |
---|---|---|---|
識別子 | identifier (5.10) | 通常文脈の"名前" | 英数字などによる文字列で先頭が数字以外 |
テンプレートID | template-id (17.2) | テンプレート特殊化 | 識別子<テンプレート実引数リスト> |
ネストされた名前修飾子 | nested-name-specifier (8.1.4.2) | 後続する識別子の"宣言領域" | 名前空間名とクラス名を::で連結して::で終わる |
修飾された識別子 | qualified-id (8.1.4.2) | 宣言領域を明示した識別子 | ネストされた名前修飾子と識別子を連結 |
"名前"は通常文脈に従い識別子を指すことにする。"テンプレート名"はテンプレートの識別子である。一方で"クラス名"と"関数名"はそれぞれの識別子とテンプレートIDを合わせたものとする。"型名"はクラス名とそれ以外の型識別子を合わせた型一般を指す。
テンプレートの文脈で"実体化(instantiation)"と"特殊化(specialization)"、"暗黙的(inplicit)"と"明示的(explicit)"を説明する。
テンプレートは一つのエンティティとして(定義でない)宣言あるいは定義され翻訳単位のODR範囲を持つ(N4659 6/p1,p6)。"特殊化"はテンプレートが定義するクラス群/関数群の要素で(17/p4)、すなわちクラス/関数である。"テンプレートの定義"と"テンプレートが定義する群"の違いを意識したい。テンプレートの定義からコンパイラが生成した特殊化が"暗黙的特殊化"で、テンプレートの定義によらずプログラマがコーディングした特殊化が"明示的特殊化"である(17.7.3)。どちらもテンプレートが定義する群の要素としてテンプレートの宣言に従うが、明示的特殊化はテンプレートの定義と関係しない。明示的特殊化はクラス/関数として(定義でない)宣言あるいは定義されてODRに従う(17.7/p5.2)。
コンパイラが暗黙的特殊化を生成する事を"実体化"と呼ぶ。実体化の指示をコンパイラに任せる事が"暗黙的実体化"で(17.7.1)、実体化をプログラマが指示する事が"明示的実体化"である(17.7.2)。明示的実体化が指示されると暗黙的実体化は抑止される(17.7.1/p1)。クラスの暗黙的実体化はメンバ関数など(メンバ関数、メンバクラス、スコープを持つメンバ列挙型、スタティックメンバ変数、メンバテンプレート、フレンド関数)の宣言を全て実体化するが、それらの定義は実体化しない(17.7.1/p2)。
明示的実体化は"明示的実体化定義"と"明示的実体化宣言"の二形式を持つ(17.7.2/p2)。前者は暗黙的実体化を抑止した上で実体化を行い、後者は暗黙的実体化の抑止のみを行う(17.7.2/p11)。プログラムのどこにも明示的実体化定義の見当たらない明示的実体化宣言はエラーとなる。明示的実体化定義はプログラムで最大一つ存在する(17.7/p5.1)。これらはエンティティの定義/宣言と全く異なる概念である。クラスを明示的実体化定義/宣言すれば、全ての明示的特殊化でないメンバ関数なども明示的実体化定義/宣言する(17.7.2/p8)。
一般的な文法で"実体化"と"特殊化"は共に動詞(行為)の名詞形だが、テンプレートの文脈で前者はある行為自体を、後者はその行為の結果を指す。実体化はコンパイラがクラス/関数を生成する行為であり、その結果が特殊化(暗黙的特殊化)である。C++オブジェクトの文脈にも"実体化"が存在してクラスがインスタンスに実体化するが、そのアナロジーとしてテンプレートが特殊化に実体化するとも言える。この言明はしかし明示的特殊化を含めれば正しくない。テンプレートの文脈で"インスタンス(instance)"は特殊化と同義とされるが(C++ Templates, p.511)、同じ理由で本サイトはこれを採らない。本サイトはテンプレートが定義するクラス群に属するクラスを"クラステンプレート特殊化"、関数群に属する関数を"関数テンプレート特殊化"として参照する。
テンプレートの文脈で"暗黙的"はコンパイラによる行為を、"明示的"はプログラマによる行為を指す。テンプレートのもたらす複雑な問題はコンパイラによる行為に起因し、テンプレートを考察するで詳細に検討する。
クラス/関数テンプレートの定義中(主にテンプレート名に続く波括弧{}の中)でテンプレート仮引数(例えばT)に依存する識別子(名前)は、Tが未定である以上それが変数名/関数名なのか型名なのか区別できない。そこで変数名/関数名をデフォルトとしてtypenameキーワードを前置した場合を型名とする(17.6/p2)。このtypenameキーワードはテンプレート仮引数リストに用いる同名のキーワードと無関係なので"型名曖昧回避子(typename disambiguator)"と呼んで区別する。なお"曖昧回避子"は当サイトが"disambiguator"の訳語として造語したもので一般的な用語ではない。型名曖昧回避子は修飾された識別子に前置する。
クラス/関数テンプレートの定義中の特定の文脈においてTに依存する識別子の後に文字"<"が現れた場合、その識別子が比較演算子に前置する変数名なのかテンプレートIDを構成するテンプレート名なのか自明とならない。その場合は変数名である事をデフォルトとしてtemplateキーワードを前置した場合をテンプレート名とする(17.2/p4)。このtemplateキーワードはテンプレート宣言で前置する同名のキーワードと無関係なので"テンプレート曖昧回避子(template disambiguator)"と呼んで区別する。テンプレート曖昧回避子はテンプレートIDに前置する。テンプレート曖昧回避子は修飾された識別子に含まれるテンプレートIDに前置するか、あるいはメンバテンプレート特殊化のテンプレートIDに前置する。
クラステンプレートの基底クラスがテンプレート仮引数(例えばT)に依存する(例えばTそれ自体)場合、クラステンプレート内の宣言されていない識別子(例えばa1)がクラステンプレートの名前空間に属するものか基底クラスのメンバなのか区別できない。この場合は名前空間に属する事をデフォルトとして、基底クラスのメンバはthisポインタで参照(this->a1)あるいは基底クラスで修飾(T::a1)する。thisポインタ参照と基底クラス修飾はほとんどの場合に等価だがそうでない場合もある。
曖昧でない文脈で曖昧回避子を使用する必要はないが、許される場合と許されない場合がある。その規格の理解は容易でなく、mingw-w64も完全準拠できていないようだ。
テンプレートは仮引数リストあるいは実引数リストを山括弧<>で囲む。山括弧の構成文字"<"と">"は比較演算子でも用いられ(N4659 8.9)、シフト演算子でも"<<"と">>"として用いられる(8.8)。ダイグラフと呼ばれる2文字で1文字を代替表記する際にも用いられれ(5.5)、例えば"<:"は"["に置き換えられる。
テンプレートIDの山括弧は必ず比較演算子より優先して解釈される。C++03以前はシフト演算子とダイグラフが山括弧に優先していたため空白文字で明示する必要があったが、C++11以降で改善された(N3242 14.2/p3、N4659 17.2/p4)。逆に比較演算子がテンプレートID山括弧の一部と解釈される場合があり、これは今も変わらず括弧で明示する必要がある。