テンプレートの部分特殊化
特定のカテゴリのテンプレート引数に対してクラスおよび変数 (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 {}; // プライマリテンプレート 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、 T2 がポインタ、 I が 5 // である場合の部分特殊化 template<class X, class T, int I> class A<X, T*, I> {}; // #4: T2 がポインタである場合の部分特殊化
標準ライブラリにおける部分特殊化の例には std::unique_ptr の配列型に対する部分特殊化などがあります。
[編集] 実引数リスト
テンプレートの部分特殊化の実引数リストには以下の制限が適用されます。
template<class T1, class T2, int I> class B {}; // プライマリテンプレート template<class X, class Y, int N> class B<X,Y,N> {}; // エラー
また、プライマリテンプレートより特殊化されている必要があります。 template<int N, typename T1, typename... Ts> struct B; template<typename... Ts> struct B<0, Ts...> { }; // エラー、より特殊化されていません。 |
(C++14以上) |
template <int I, int J> struct A {}; template <int I> struct A<I+5, I*2> {}; // エラー、 I は推定できません。 template <int I, int J, int K> struct B {}; template <int I> struct B<I, I*2, 2> {}; // OK、第1引数が推定可能です。
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以上)テンプレートが実体化され、利用可能な特殊化がある場合、コンパイラはプライマリテンプレートを使用するか、その特殊化のいずれかを使用するかを、決定する必要があります。
// このページの一番最初の例のクラステンプレート A が定義されているとします。 A<int, int, 1> a1; // マッチする特殊化はありません。 プライマリテンプレートを使用します。 A<int, int*, 1> a2; // 部分特殊化 #1 (T=int, I=1) を使用します。 A<int, char*, 5> a3; // 部分特殊化 #3 (T=char) を使用します。 A<int, char*, 1> a4; // 部分特殊化 #4 (X=int, T=char, I=1) を使用します。 A<int*, int*, 2> a5; // エラー、 #2 (T=int, T2=int*, I=2) と // #4 (X=int*, T=int, I=2) にマッチしますが、 // いずれも他方より特殊化されていません。
非形式的には、「A が B より特殊化されている」は、「A が受理する型は B が受理する型の部分集合である」という意味です。
形式的には、部分特殊化間の「より特殊化されている」関係を構築するために、まず、それぞれを以下のような架空の関数テンプレートに変換します。
- 1つめの関数テンプレートは、1つめの部分特殊化と同じテンプレート仮引数と、1つめの部分特殊化の実引数を用いたクラステンプレートの特殊化を型とする1個の関数引数を持ちます。
- 2つめの関数テンプレートは、2つめの部分特殊化と同じテンプレート引数と、2つめの部分特殊化の実引数を用いたクラステンプレートの特殊化を型とする1個の関数引数を持ちます。
そして、その関数テンプレートが、関数テンプレートのオーバーロードの場合と同様に、順位付けされます。
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 の両方にマッチします。 // 架空の関数テンプレートに対する半順序: // #B から #A: void(X<U1, U1, int>) から void(X<I,J,int>) : 推定成功 // #A から #B: void(X<U1, U2, int>) から void(X<I,I,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 1315 | C++14 | template parameter could not be used in non-type arg expressions other than id-expressions | expressions ok as long as deducible |