パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.7.5.3(3)
本サイトは移転しました。旧アドレスからのリダイレクトは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)によるテンプレート用語に従う。

template<typename T> // "typename T"はテンプレート仮引数リスト
class Heap; // "Heap"はテンプレート名
template<typename T> // "typename T"はテンプレート仮引数リスト
class Heap<T*>; // "Heap<T*>"はテンプレートID
template<>
class Heap<char*>; // "Heap"はテンプレート名、"char*"はテンプレート実引数リスト
Heap<int> aHeap; // "int"はテンプレート実引数リスト
Heap<char*> aHeap2; // "Heap<char*>"はテンプレートID
tempalate<typename T> // "typename T"はテンプレート仮引数リスト
void print(const T& x) {...} // "print"はテンプレート名

より広範囲かつ正確には別教科書(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)。

template<typename T> class A; // クラステンプレートAの(定義でない)宣言
template<typename T> class A {void f();}; // Aの定義
template<typename T> void A<T>::f() {} // Aのメンバ関数fの定義
template<typename T> class A<T*> {void g();}; // Aのポインタ型による部分特殊化
template<> class A<char> {void h();}; // Aのcharによる明示的特殊化、A<char>クラスの定義
extern template class A<int>; // Aのintによる明示的実体化宣言
template class A<int>; // Aの明示的実体化定義でintによる暗黙的特殊化、ただしA<int>クラスの定義でない
A<char> a1; // A<char>インスタンスの定義
A<int> a2; // A<int>インスタンスの定義
A<double> a3; // Aの暗黙的実体化でdoubleによる暗黙的特殊化、A<double>インスタンスの定義
template<typename T> void f(T); // 関数テンプレートfの(定義でない)宣言
template<typename T> void f(T) {} // fの定義
template<typename T> void f(T*) {} // fにオーバーロードされる関数テンプレートの定義
void f(char) {} // fにオーバーロードされる関数の定義
template<> void f(int) {} // fのint(テンプレート実引数推定(N4659 17.7.2/p7))による明示的特殊化、f<int>関数の定義
extern template void f(double); // fのdoubleによる明示的実体化宣言
template void f(double); // fの明示的実体化定義でdoubleによる暗黙的特殊化、ただしf<double>関数の定義でない
class B {};
void g() {B b;f(b);} // fの暗黙的実体化でクラスBによる暗黙的特殊化、f<B>関数をコール

一般的な文法で"実体化"と"特殊化"は共に動詞(行為)の名詞形だが、テンプレートの文脈で前者はある行為自体を、後者はその行為の結果を指す。実体化はコンパイラがクラス/関数を生成する行為であり、その結果が特殊化(暗黙的特殊化)である。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ポインタ参照と基底クラス修飾はほとんどの場合に等価だがそうでない場合もある。

struct A // クラスA
{
struct X {}; // メンバクラス(ネストクラス)
template<typename> struct Y // メンバクラステンプレート
{
virtual ~Y() {}
virtual void y1() {} // メンバクラステンプレートの仮想メンバ関数
};
void a1() {} // メンバ関数
template<typename> void a2() {} // メンバ関数テンプレート
};
template<typename T> void f() // 関数テンプレートf
{
typename T::X x; // T::XはT依存なので型名曖昧回避子(typename)が必要、T=AならA::X型変数の定義
typename T::Y<int> y; // T::Y<int>はT依存、T=AならA::Y<int>型変数の定義、テンプレートID自明でテンプレート曖昧回避子は不要
typename T::template Y<int> yy; // テンプレート曖昧回避子(template)を付けても良い
T a; // aはTのインスタンス(T依存変数)
a.a1(); // T=AならA::a1()をコール
a.template a2<int>(); // T=AならA::a2<int>()をコール、テンプレートID自明とならずテンプレート曖昧回避子が必要
}
template<typename T> struct B:private T // クラステンプレートB、基底がT(基底がT依存クラス)
{
void b1() // メンバ関数
{
this->a1(); // 基底のメンバ関数a1()をコール、T=AならA::a1()をコール、メンバ関数を明示するためthis->を付加
T::a1(); // 上と等価、メンバ関数を明示するためT::を付加
this->template a2<int>(); // 基底のメンバ関数テンプレート特殊化をコール、T=AならA::a2<int>()をコール、テンプレート曖昧回避子が必要
T::template a2<int>(); // 上と等価、テンプレート曖昧回避子が必要
}
};
struct Z:A::Y<int> // クラスZ、A::Y<int>から派生
{
void y1() override {} // 仮想メンバ関数y1()のオーバーライド
};
template<typename T> void g() // 関数テンプレートg
{
typename T::Y<int>* py=new Z; // Zインスタンスを基底クラスポインタで保持
py->y1(); // T=AならA::Y<int>::y1()の仮想コールでZ::y1()をコール
py->T::template Y<int>::y1(); // T=AならA::Y<int>::y1()の直接コール、上と等価でない、テンプレート曖昧回避子が必要
delete py; // A::a2<int>()はテンプレート特殊化なので仮想にできないが、A::Y<int>::y1()はできる
}

曖昧でない文脈で曖昧回避子を使用する必要はないが、許される場合と許されない場合がある。その規格の理解は容易でなく、mingw-w64も完全準拠できていないようだ。

// mingw-w64でエラーとなる行をコメントアウトしている
// OK(?)は規格上OKと思われるがmingw-w64はエラー、エラー(?)は規格上エラーと思われるがmingw-w64はOK
//
// 修飾された識別子においてテンプレート実引数リストなどの外に現れるテンプレート曖昧回避子を"トップレベル"にあると呼び、
// そういった曖昧回避子はクラスヘッド名(class-head-name)や列挙子ヘッド名(enum-head-name)に現れてはならないが、
// 型名指定子(typename-specifier)、詳述型指定子(elaborated-type-specifier)、using宣言(using-declaration)、
// 基底クラス指定(class-or-decltype)では無視される(N4659 17.2/p4)、C++17で追加された規定で詳しくは以下参照
// https://cplusplus.github.io/CWG/issues/1710.html
template<typename> struct A // クラステンプレートA
{
struct B{}; // メンバクラス
template<typename> struct C{}; // メンバクラステンプレート
template<typename> void fa() {} // メンバ関数テンプレート
};
template<typename T> void f() // 関数テンプレートf
{
//A<T>::B b1; // エラー、A<T>::BはT依存なので型名曖昧回避子(typename)が必要
typename A<T>::B b2; // OK
class A<T>::B b3; // OK、詳述型指定子は型名曖昧回避子が不要
//class typename A<T>::B b4; // エラー、詳述型指定子は型名曖昧回避子を含んではならない
//A<T>::C<int> c1; // エラー
typename A<T>::C<int> c2; // OK、修飾された識別子でテンプレート曖昧回避子(template)は不要
typename A<T>::template C<int> c3; // OK、型名指定子で"トップレベル"テンプレート曖昧回避子は無視される
//class A<T>::C<int> c4; // OK(?)、詳述型指定子によるT依存クラステンプレート特殊化
//class typename A<T>::C<int> c5; // エラー
//class A<T>::template C<int> c6; // OK(?)、詳述型指定子で"トップレベル"テンプレート曖昧回避子は無視される
A<T> a; // A<T>インスタンス、T依存
//a.fa<int>(); // エラー、aはT依存なのでテンプレート曖昧回避子が必要
a.template fa<int>(); // OK
//a.A<T>::fa<int>(); // エラー
a.A<T>::template fa<int>(); // OK
}
void g() // 関数g、fのT=intとしたテンプレートでない関数
{
A<int>::B b1; // OK
typename A<int>::B b2; // OK、型名曖昧回避子は冗長
class A<int>::B b3; // OK
//class typename A<int>::B b4; // エラー
A<int>::C<int> c1; // OK
typename A<int>::C<int> c2; // OK
typename A<int>::template C<int> c3; // OK
class A<int>::C<int> c4; // OK、詳細型指定子によるクラステンプレート特殊化
//class typename A<int>::C<int> c5; // エラー
class A<int>::template C<int> c6; // OK
A<int> a; // A<int>インスタンス
a.fa<int>(); // OK
a.template fa<int>(); // OK
a.A<int>::fa<int>(); // OK
a.A<int>::template fa<int>(); // OK
}
template<typename T> struct D // クラステンプレートD
:private A<T>::B {}; // OK、基底クラス指定は型名曖昧回避子が不要
//template<typename T> struct E // クラステンプレートE
// :private typename A<T>::B {}; // エラー、基底クラス指定は型名曖昧回避子を含んではならない
template<typename T> struct F // クラステンプレートF
:private A<T>::C<int> {}; // OK、テンプレート曖昧回避子は不要
template<typename T> struct G // クラステンプレートG
:private A<T>::template C<int> {}; // OK、基底クラス指定で"トップレベル"テンプレート曖昧回避子は無視される
namespace N // 名前空間N
{
template<typename> struct I {struct J{};}; // 名前空間内のクラステンプレート
template<typename T> void h() // 名前空間内の関数テンプレート
{
typename I<T>::J j1; // OK
}
}
template<typename T> void i() // 関数テンプレートi
{
//N::typename I<T>::J j2; // エラー、ネストされた名前修飾子は型名曖昧回避子を直接含んではならない
typename N::I<T>::J j3; // OK
}
template<> struct N::I<char>::J {}; // OK、名前空間内のクラステンプレートのネストクラスの明示的特殊化
template<> struct N::template I<int>::J {}; // エラー(?)、クラスヘッド名は"トップレベル"テンプレート曖昧回避子を含んではならない

山括弧のもたらす曖昧さ

テンプレートは仮引数リストあるいは実引数リストを山括弧<>で囲む。山括弧の構成文字"<"と">"は比較演算子でも用いられ(N4659 8.9)、シフト演算子でも"<<"と">>"として用いられる(8.8)。ダイグラフと呼ばれる2文字で1文字を代替表記する際にも用いられれ(5.5)、例えば"<:"は"["に置き換えられる。

覚え書き
本記事と無関係だがトライグラフと呼ばれる3文字で1文字を代替表記するものもC++14まで存在した(N3797 2.4)。C++17でこれは廃止された(N4659 C4.1)。

テンプレートIDの山括弧は必ず比較演算子より優先して解釈される。C++03以前はシフト演算子とダイグラフが山括弧に優先していたため空白文字で明示する必要があったが、C++11以降で改善された(N3242 14.2/p3、N4659 17.2/p4)。逆に比較演算子がテンプレートID山括弧の一部と解釈される場合があり、これは今も変わらず括弧で明示する必要がある。

struct A{}; // クラスA、グローバル名前空間で定義
template<typename> struct B{}; // クラステンプレートB
template<bool> struct C{}; // クラステンプレートC、テンプレート仮引数はbool
void f()
{
B<A> b1; // OK
B<::A> b2; // OK、C++03以前はエラー、"B[:A> b2"に置き換えられた
B< ::A> b3; // OK、C++03以前でもOK
B<B<A>> b4; // OK、C++03以前はエラー、"((B<B)<A) >> b4"と解釈された
B<B<A> > b5; // OK、C++03以前でもOK
C<true> c1; // OK
//C<1>0> c2; // エラー、"((C<1>)0) > c2"と解釈される
C<(1>0)> c3; // OK
}