Partial template specialization
クラス および変数 (C++14以降) テンプレートを、特定のカテゴリのテンプレート引数に対してカスタマイズすることを可能にします。
目次 |
構文
template
<
parameter-list
>
class-key
class-head-name
<
argument-list
>
declaration
|
(1) | ||||||||
template
<
parameter-list
>
decl-specifier-seq
declarator
<
argument-list
>
initializer
(オプション)
|
(2) | (C++14以降) | |||||||
class-head-name は、事前に宣言された class template の名前を識別し、 declarator は、事前に宣言された variable template の名前を識別します (C++14以降) 。
部分特殊化は、その主テンプレートが定義可能な任意のスコープで宣言できます(これは主テンプレートが定義されたスコープとは異なる場合があります。例えば、 メンバーテンプレート のクラス外特殊化など)。部分特殊化は非特殊化テンプレート宣言の後に記述されなければなりません。
例えば、
template<class T1, class T2, int I> class A {}; // プライマリテンプレート template<class T, int I> class A<T, T*, I> {}; // #1: T2がT1へのポインタである部分特殊化 template<class T, class T2, int I> class A<T*, T2, I> {}; // #2: T1がポインタである部分特殊化 template<class T> class A<int, T*, 5> {}; // #3: T1がint、Iが5、T2がポインタである // 部分特殊化 template<class X, class T, int I> class A<X, T*, I> {}; // #4: T2がポインタである部分特殊化
標準ライブラリにおける部分特殊化の例としては、配列型に対する部分特殊化を持つ std::unique_ptr が挙げられます。
引数リスト
部分テンプレート特殊化の argument-list には以下の制限が適用されます:
template<class T1, class T2, int I> class B {}; // primary template template<class X, class Y, int N> class B<X, Y, N> {}; // error
|
さらに、特殊化はプライマリテンプレートよりもより特殊化されている必要がある template<int N, typename T1, typename... Ts> struct B; template<typename... Ts> struct B<0, Ts...> {}; // Error: not more specialized |
(C++11以降) |
template<int I, int J> struct A {}; template<int I> struct A<I + 5, I * 2> {}; // error, I is not deducible template<int I, int J, int K> struct B {}; template<int I> struct B<I, I * 2, 2> {}; // OK: first parameter is deducible
template<class T, T t> struct C {}; // プライマリテンプレート template<class T> struct C<T, 1>; // エラー: 引数1の型はTであり、 // これはパラメータTに依存する template<int X, int (*array_ptr)[X]> class B {}; // プライマリテンプレート int array[5]; template<int X> class B<X, &array> {}; // エラー: 引数&arrayの型は // int(*)[X]であり、これはパラメータXに依存する
名前探索
部分テンプレート特殊化は名前探索では見つかりません。プライマリテンプレートが名前探索で見つかった場合にのみ、その部分特殊化が考慮されます。特に、プライマリテンプレートを可視化するusing宣言は、部分特殊化も同様に可視化します:
namespace N { template<class T1, class T2> class Z {}; // プライマリテンプレート } using N::Z; // プライマリテンプレートを参照 namespace N { template<class T> class Z<T, T*> {}; // 部分特殊化 } Z<int, int*> z; // 名前探索は N::Z(プライマリテンプレート)を見つけ、 // T = int の部分特殊化が使用される
部分順序付け
クラス または変数 (C++14以降) テンプレートがインスタンス化され、部分特殊化が利用可能な場合、コンパイラはプライマリテンプレートを使用するか、その部分特殊化のいずれかを使用するかを決定する必要があります。
// given the template A as defined above A<int, int, 1> a1; // no specializations match, uses primary template A<int, int*, 1> a2; // uses partial specialization #1 (T = int, I = 1) A<int, char*, 5> a3; // uses partial specialization #3, (T = char) A<int, char*, 1> a4; // uses partial specialization #4, (X = int, T = char, I = 1) A<int*, int*, 2> a5; // error: matches #2 (T = int, T2 = int*, I= 2) // matches #4 (X = int*, T = int, I = 2) // neither one is more specialized than the other
非公式には「A は B より特殊化されている」とは、「A が B が受け入れる型の部分集合を受け入れる」ことを意味します。
形式的には、部分特殊化間の「より特殊化されている」関係を確立するために、まず各特殊化を以下のように仮想的な関数テンプレートに変換します:
- 最初の関数テンプレートは最初の部分特殊化と同じテンプレートパラメータを持ち、最初の部分特殊化からのすべてのテンプレート引数を持つクラステンプレートの特殊化を型とする単一の関数パラメータを持つ
- 2番目の関数テンプレートは2番目の部分特殊化と同じテンプレートパラメータを持ち、2番目の部分特殊化からのすべてのテンプレート引数を持つクラステンプレートの特殊化を型とする単一の関数パラメータを持つ
関数テンプレートはその後、 function template overloading の場合と同様にランク付けされます。
template<int I, int J, class T> struct X {}; // プライマリテンプレート template<int I, int J> struct X<I, J, int> { static const int s = 1; }; // 部分特殊化 #1 // #1のための仮想的な関数テンプレートは // template<int I, int J> void f(X<I, J, int>); #A template<int I> struct X<I, I, int> { static const int s = 2; }; // 部分特殊化 #2 // #2のための仮想的な関数テンプレートは // template<int I> void f(X<I, I, int>); #B int main() { X<2, 2, int> x; // #1と#2の両方がマッチ // 関数テンプレートの部分順序付け: // #Aから#B: void(X<I, J, int>)からvoid(X<U1, U1, int>): 推論成功 // #Bから#A: void(X<I, I, int>)からvoid(X<U1, U2, int>): 推論失敗 // #Bの方がより特殊化されている // #2がインスタンス化される特殊化 std::cout << x.s << '\n'; // 2を出力 }
部分特殊化のメンバー
部分特殊化のメンバーのテンプレートパラメータリストとテンプレート引数リストは、部分特殊化のパラメータリストと引数リストと一致しなければなりません。
プライマリテンプレートのメンバと同様に、それらはプログラム内で使用される場合にのみ定義する必要があります。
部分特殊化のメンバーは、プライマリテンプレートのメンバーとは関連しません。
部分特殊化のメンバーの明示的(完全)特殊化は、プライマリテンプレートの明示的特殊化と同じ方法で宣言されます。
template<class T, int I> // プライマリテンプレート struct A { void f(); // メンバ宣言 }; template<class T, int I> void A<T, I>::f() {} // プライマリテンプレートのメンバ定義 // 部分特殊化 template<class T> struct A<T, 2> { void f(); void g(); void h(); }; // 部分特殊化のメンバ template<class T> void A<T, 2>::g() {} // 明示的(完全)特殊化 // 部分特殊化のメンバの template<> void A<char, 2>::h() {} int main() { A<char, 0> a0; A<char, 2> a2; a0.f(); // OK、プライマリテンプレートのメンバ定義を使用 a2.g(); // OK、部分特殊化のメンバ定義を使用 a2.h(); // OK、部分特殊化のメンバの完全特殊化定義を使用 a2.f(); // エラー:部分特殊化 A<T,2> に f() の定義が存在しない // (プライマリテンプレートは使用されない) }
プライマリテンプレートが別のクラステンプレートのメンバである場合、その部分特殊化は外側のクラステンプレートのメンバとなります。外側のテンプレートがインスタンス化されると、各メンバ部分特殊化の宣言も同様にインスタンス化されます(テンプレートの他のすべてのメンバの宣言がインスタンス化されるのと同じ方法で、ただし定義は除きます)。
プライマリメンバーテンプレートが、外側のクラステンプレートの特定の(暗黙の)特殊化に対して明示的に(完全に)特殊化されている場合、その外側のクラステンプレートの特殊化に対しては、メンバーテンプレートの部分特殊化は無視されます。
メンバーテンプレートの部分特殊化が、外側のクラステンプレートの特定の(暗黙的な)特殊化に対して明示的に特殊化されている場合でも、主要なメンバーテンプレートとその他の部分特殊化は、この外側のクラステンプレートの特殊化に対して引き続き考慮されます。
template<class T> struct A // 外側のクラステンプレート { template<class T2> struct B {}; // プライマリメンバーテンプレート template<class T2> struct B<T2*> {}; // メンバーテンプレートの部分特殊化 }; template<> template<class T2> struct A<short>::B {}; // プライマリメンバーテンプレートの完全特殊化 // (部分特殊化は無視される) A<char>::B<int*> abcip; // 部分特殊化 T2=int を使用 A<short>::B<int*> absip; // プライマリの完全特殊化を使用 (部分特殊化は無視) A<char>::B<int> abci; // プライマリを使用
不具合報告
以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。
| DR | 適用対象 | 公開時の仕様 | 修正後の仕様 |
|---|---|---|---|
| CWG 727 | C++98 |
部分特殊化と完全特殊化がクラススコープで
許可されていなかった |
任意のスコープで許可 |
| CWG 1315 | C++98 |
テンプレートパラメータをid式以外の定数
テンプレート引数で使用できなかった |
推論可能な範囲で式を許可 |
| CWG 1495 | C++11 | パラメータパックを含む場合の仕様が不明確 | 特殊化はより特殊化されている必要がある |
| CWG 1711 | C++14 | 変数テンプレートの部分特殊化に関する仕様が不足 | 変数テンプレートのサポートを追加 |
| CWG 1819 | C++98 | 部分特殊化の定義が可能なスコープ |
部分特殊化を主テンプレートと同じ
スコープで宣言可能に変更 |
| CWG 2330 | C++14 | 変数テンプレートへの参照が不足 | 変数テンプレートのサポートを追加 |