部分テンプレート特殊化
特定のカテゴリのテンプレート引数に対して、クラスと変数(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 は以前に宣言されたクラステンプレートの名前を識別し、declarator は以前に宣言された変数テンプレートの名前を識別します(C++14以降)。
部分特殊化は、そのプライマリテンプレートが定義できる任意のスコープで宣言できます(これはプライマリテンプレートが定義されるスコープとは異なる場合があります。例えば、メンバテンプレートのクラス外特殊化の場合など)。部分特殊化は、非特殊化テンプレート宣言の後に記述する必要があります。
例えば、
template<class T1, class T2, int I> class A {}; // primary template template<class T, int I> class A<T, T*, I> {}; // #1: partial specialization where T2 is a pointer to T1 template<class T, class T2, int I> class A<T*, T2, I> {}; // #2: partial specialization where T1 is a pointer template<class T> class A<int, T*, 5> {}; // #3: partial specialization where // T1 is int, I is 5, and T2 is a pointer template<class X, class T, int I> class A<X, T*, I> {}; // #4: partial specialization where T2 is a pointer
標準ライブラリにおける部分特殊化の例には、配列型に対する部分特殊化を持つ 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 {}; // primary template template<class T> struct C<T, 1>; // error: type of the argument 1 is T, // which depends on the parameter T template<int X, int (*array_ptr)[X]> class B {}; // primary template int array[5]; template<int X> class B<X, &array> {}; // error: type of the argument &array is // int(*)[X], which depends on the parameter X
[編集] 名前探索
部分テンプレート特殊化は名前探索によって見つかりません。プライマリテンプレートが名前探索によって見つかった場合にのみ、その部分特殊化が考慮されます。特に、プライマリテンプレートを可視にするusing宣言は、部分特殊化も可視にします。
namespace N { template<class T1, class T2> class Z {}; // primary template } using N::Z; // refers to the primary template namespace N { template<class T> class Z<T, T*> {}; // partial specialization } Z<int, int*> z; // name lookup finds N::Z (the primary template), the // partial specialization with T = int is then used
[編集] 部分順序付け
クラスまたは変数(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が受け入れる型のサブセットを受け入れる」という意味です。
正式には、部分特殊化間のより特殊化された関係を確立するために、それぞれが最初に次のように架空の関数テンプレートに変換されます。
- 最初の関数テンプレートは、最初の部分特殊化と同じテンプレートパラメータを持ち、1つの関数パラメータのみを持ち、その型は最初の部分特殊化のすべてのテンプレート引数を持つクラステンプレート特殊化です。
- 2番目の関数テンプレートは、2番目の部分特殊化と同じテンプレートパラメータを持ち、1つの関数パラメータのみを持ち、その型は2番目の部分特殊化のすべてのテンプレート引数を持つクラステンプレート特殊化です。
関数テンプレートは、関数テンプレートのオーバーロードの場合と同様にランク付けされます。
template<int I, int J, class T> struct X {}; // primary template template<int I, int J> struct X<I, J, int> { static const int s = 1; }; // partial specialization #1 // fictitious function template for #1 is // 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; }; // partial specialization #2 // fictitious function template for #2 is // template<int I> void f(X<I, I, int>); #B int main() { X<2, 2, int> x; // both #1 and #2 match // partial ordering for function templates: // #A from #B: void(X<I, J, int>) from void(X<U1, U1, int>): deduction OK // #B from #A: void(X<I, I, int>) from void(X<U1, U2, int>): deduction fails // #B is more specialized // #2 is the specialization that is instantiated std::cout << x.s << '\n'; // prints 2 }
[編集] 部分特殊化のメンバ
部分特殊化のメンバのテンプレートパラメータリストとテンプレート引数リストは、部分特殊化のパラメータリストと引数リストに一致しなければなりません。
プライマリテンプレートのメンバと同様に、プログラムで使用された場合にのみ定義する必要があります。
部分特殊化のメンバは、プライマリテンプレートのメンバとは関連付けられていません。
部分特殊化のメンバの明示的(完全)特殊化は、プライマリテンプレートの明示的特殊化と同じ方法で宣言されます。
template<class T, int I> // primary template struct A { void f(); // member declaration }; template<class T, int I> void A<T, I>::f() {} // primary template member definition // partial specialization template<class T> struct A<T, 2> { void f(); void g(); void h(); }; // member of partial specialization template<class T> void A<T, 2>::g() {} // explicit (full) specialization // of a member of partial specialization template<> void A<char, 2>::h() {} int main() { A<char, 0> a0; A<char, 2> a2; a0.f(); // OK, uses primary template’s member definition a2.g(); // OK, uses partial specialization's member definition a2.h(); // OK, uses fully-specialized definition of // the member of a partial specialization a2.f(); // error: no definition of f() in the partial // specialization A<T,2> (the primary template is not used) }
プライマリテンプレートが別のクラステンプレートのメンバである場合、その部分特殊化は囲むクラステンプレートのメンバです。囲むテンプレートがインスタンス化されると、各メンバの部分特殊化の宣言もインスタンス化されます(テンプレートの他のすべてのメンバの宣言はインスタンス化されますが、定義はインスタンス化されないのと同じ方法です)。
プライマリメンバテンプレートが、囲むクラステンプレートの特定の(暗黙的)特殊化に対して明示的に(完全に)特殊化されている場合、そのメンバテンプレートの部分特殊化は、囲むクラステンプレートのこの特殊化に対して無視されます。
メンバテンプレートの部分特殊化が、囲むクラステンプレートの特定の(暗黙的)特殊化に対して明示的に特殊化されている場合、プライマリメンバテンプレートとその他の部分特殊化は、囲むクラステンプレートのこの特殊化に対して引き続き考慮されます。
template<class T> struct A // enclosing class template { template<class T2> struct B {}; // primary member template template<class T2> struct B<T2*> {}; // partial specialization of member template }; template<> template<class T2> struct A<short>::B {}; // full specialization of primary member template // (will ignore the partial) A<char>::B<int*> abcip; // uses partial specialization T2=int A<short>::B<int*> absip; // uses full specialization of the primary (ignores partial) A<char>::B<int> abci; // uses primary
[編集] 欠陥報告
以下の動作変更を伴う欠陥報告が、以前に公開されたC++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 727 | C++98 | 部分特殊化と完全特殊化は許可されない クラススコープ |
任意のスコープで許可される |
| CWG 1315 | C++98 | テンプレートパラメータは非型で使用できなかった id-expression以外のテンプレート引数 |
推論可能であれば式はOK |
| CWG 1495 | C++11 | パラメータパックを含む場合の仕様が不明確だった | 特殊化はより特殊化されている必要がある |
| CWG 1711 | C++14 | 変数テンプレートの部分特殊化の仕様が不足 | 変数テンプレートのサポートを追加 |
| CWG 1819 | C++98 | 部分特殊化の定義に許容されるスコープ | 部分特殊化を宣言できるようにする プライマリテンプレートと同じスコープで |
| CWG 2330 | C++14 | 変数テンプレートへの参照が不足 | 変数テンプレートのサポートを追加 |