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

テンプレートの部分特殊化 (Partial template specialization)は、C++の用語で、テンプレートにおいて、特定のテンプレート実引数が与えられたときに、元と異なる定義を使用させるようにする仕組みである。

テンプレートと明示的特殊化

テンプレートはメタクラスである。つまりコンパイラに対してどのようなクラスを作るかを指示したある種の抽象データ型であると言える。たとえばテンプレートであるvector(動的配列)をプログラマが使うときには、vector<int>vector<string>などのようにデータ型を指定して実体化する。実体化されたvectorは、コンパイラの生成したオブジェクトコードの中ではそれぞれ別のコードが生成され、それぞれ別のクラスとして扱われる。

もしテンプレートクラスは特定のデータ型を指定して使われることが多いと知っており、そのデータ型の場合にだけ人の手で最適化をかけられる(たとえば整数型のときは2の乗数での乗除算をシフト演算にするなど)としたら、通常の場合のテンプレートとは別に、明示的特殊化を行うことができる。テンプレートを使用する際に指定されたデータ型が、明示的特殊化されているものだった場合、コンパイラは明示的特殊化されたものをコンパイルしてコードを生成する。

たとえば、これは要素数nの配列の先頭要素を指すポインタpに対して、全ての要素をxにするということを行うC++の関数テンプレートarray_fillである。

template<typename T>
inline void array_fill(T* p, std::size_t n, T x)
{
    std::fill_n(p, n, x);     
}

これは、特殊化ではない通常のテンプレートの定義である。このような明示的特殊化でないものを一次テンプレートと言う (X3014 14.5.4)。

もしその配列がchar型だった場合、標準Cライブラリmemsetを使うことができ、その方がstd::fill_nより高速化されているとする。そのような場合、次のように明示的特殊化すれば、array_fillにchar型の先頭要素を指すポインタが渡されたときにはmemsetを用いるようにすることができる。

template<>
inline void array_fill(char* p, std::size_t n, char x)
{
    std::memset(p, x, n);
}

部分特殊化

部分特殊化は、複数の型にまたがって影響を及ぼすようになった明示的特殊化であると言える。

template<typename Container>
struct get_iterator
{
    typedef typename Container::iterator type;
};

このコードは例えば、次のように用いる。

using std::string;
string s = "abc";
get_iterator<string>::type it = std::find(s.begin(), s.end(), 'a');

ここで、次のような部分特殊化を導入する。

template<typename T, std::size_t N>
struct get_iterator<T[N]>
{
    typedef T* type;
};

get_iteratorの後ろの<T[N]>という記述で、コンパイラはこれが部分特殊化だと認識する。これによって、次のように配列に対してもget_iteratorが使用可能になる。

const int MAX = 10;
int a[MAX] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
get_iterator<int[MAX]>::type it = std::find(a, a + MAX, 5);

int[MAX]だけでなく、const char[80]やstd::complex[4]などT[N]の形に一致する型であればいずれも部分特殊化版のget_iteratorが使用される。このように、どの一次テンプレート・明示的特殊化・部分特殊化を使用するかはパターンマッチ的に決定される。

テンプレートの部分特殊化が可能なのはクラステンプレートのみである。関数テンプレートは部分特殊化できず、明示的特殊化のみ可能である。しかし多くの場合、関数テンプレートの部分特殊化は多重定義によって代用できる。

参考文献

  • JIS X3014:2003 『プログラム言語C++』