名前空間
変種
操作

テンプレートの部分特殊化

提供: cppreference.com
< cpp‎ | language
 
 
C++言語
一般的なトピック
フロー制御
条件付き実行文
繰り返し文 (ループ)
ジャンプ文
関数
関数宣言
ラムダ関数宣言
inline 指定子
例外指定 (C++20未満)
noexcept 指定子 (C++11)
例外
名前空間
指定子
decltype (C++11)
auto (C++11)
alignas (C++11)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点
文字 - 文字列 - nullptr (C++11)
ユーザ定義 (C++11)
ユーティリティ
属性 (C++11)
typedef 宣言
型エイリアス宣言 (C++11)
キャスト
暗黙の変換 - 明示的な変換
static_cast - dynamic_cast
const_cast - reinterpret_cast
メモリ確保
クラス
クラス固有の関数特性
特別なメンバ関数
テンプレート
その他
 
 

特定のカテゴリのテンプレート引数に対してクラスおよび変数 (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 の配列型に対する部分特殊化などがあります。

[編集] 実引数リスト

テンプレートの部分特殊化の実引数リストには以下の制限が適用されます。

1) 特殊化されていない実引数リストと同じであってはなりません (何かしら特殊化されていなければなりません)。
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以上)
2) デフォルト引数を指定することはできません。
3) いずれかの実引数がパック展開の場合、それはリストの最後の実引数でなければなりません。
4) 非型引数の式は、それがテンプレート仮引数の名前のみであるときを除いて、テンプレート仮引数の名前を使用してはなりません。 (C++14未満) 非型引数の式は、非推定文脈の外側で少なくとも1度現れているならば、テンプレート仮引数を使用することができます。 (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引数が推定可能です。
5) 非型テンプレート引数は、その特殊化の仮引数に依存する型のテンプレート引数を特殊化することはできません。
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以上)テンプレートが実体化され、利用可能な特殊化がある場合、コンパイラはプライマリテンプレートを使用するか、その特殊化のいずれかを使用するかを、決定する必要があります。

1) マッチする特殊化がひとつだけの場合は、その特殊化が使用されます。
2) 複数の特殊化がマッチする場合は、どの特殊化がより特殊化されているかを判定するために半順序のルールが使用されます。 最も特殊化されているものがひとつだけあれば、それが使用されます (そうでなければ、プログラムはコンパイルできません)。
3) マッチする特殊化がなければ、プライマリテンプレートが使用されます。
// このページの一番最初の例のクラステンプレート 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