Skip to content

特殊化

テンプレートでは実引数に応じて関数やクラスを生成します。

// 関数テンプレート
template <typename T>
T Sum(T a, T b) {
    return a + b;
}

// 関数テンプレートの関数の呼び出し
Sum<int>(1, 2);

Sum<int>(1, 2) という関数テンプレートの関数の呼び出しによって Tint である関数が必要と判断され、次の関数が生成されます。

int Sum(int a, int b) {
    return a + b;
}

このようにテンプレートを使用する箇所において、 関数テンプレートから関数を生成することおよび クラステンプレートからクラスを生成することを特殊化 (または暗黙的インスタンス化) といいます。

特殊化はコンパイラによって行われるため、 ヘッダファイルで関数テンプレートを使用する場合にはそのヘッダファイルで定義も行います。

#ifndef SUM_H_
#define SUM_H_

template <typename T>
inline T Sum(T a, T b) {  // inline 指定が必要
    return a + b;
}

#endif  // SUM_H_
#include <iostream>

#include "sum.h"

int main() {
    std::cout << Sum(1, 2) << std::endl;
    return 0;
}
テンプレートの明示的インスタンス化

テンプレートを使用する箇所で関数やクラスを生成する代わりに、 ソースファイルで明示的に関数やクラスを生成することで ヘッダファイルでは宣言だけ行うことは可能ではあります。

#ifndef SUM_H_
#define SUM_H_

template <typename T>
T Sum(T a, T b);  // 宣言だけ行う (inline もつけない)

#endif  // SUM_H_
#include "sum.h"

// 関数テンプレートの定義
template <typename T>
T Sum(T a, T b) {
    return a + b;
}

// 明示的インスタンス化
template int Sum<int>(int, int);

こうした構成にするとヘッダファイルでテンプレートが提供されていても 使用可能な型はソースファイルで明示的な生成を行う型のみとなってしまいます。 たとえば Sum<double>(double, double) は生成されていないため Sum(1.2, 3.4) のように関数テンプレートの関数を呼び出すとリンクエラーになります。

こうした問題を避けるためには、 ヘッダファイルでは関数テンプレートをやめてオーバーロードされた関数を提供し、 ソースファイルで関数テンプレートを使用します。

#ifndef SUM_H_
#define SUM_H_

int Sum(int a, int b);
double Sum(double a, double b);

#endif  // SUM_H_
#include "sum.h"

// 実装に関数テンプレートを使用
template <typename T>
T SumImpl(T a, T b) {
    return a + b;
}

int Sum(int a, int b) {
    return SumImpl(a, b);
}

double Sum(double a, double b) {
    return SumImpl(a, b);
}

完全特殊化

特殊化によって関数テンプレートやクラステンプレートから関数やクラスを生成する代わりに、 通常の関数やクラスを使用するように指定することで 特定のテンプレート引数に対する挙動を変更することができます。 これを完全特殊化 (または明示的特殊化) といいます。

関数テンプレートの完全特殊化は次のようにします。

template <typename T>
T DoSomething(T a, T b) {
    return a + b;
}

template <>
double DoSomething<double>(double a, double b) {
    return a * b;
}

std::cout << DoSomething(2, 3) << std::endl;  // 5
std::cout << DoSomething(2.0, 3.0) << std::endl;  // 6

関数の前に template <> を付けて完全特殊化を行うことを指定し、 関数名の後に < ... > で対象となるテンプレート引数を指定します。

クラステンプレートの完全特殊化も同様です。

template <typename T>
class Array {
 public:
    explicit Array(int size)
        : size_(size),
          data_(new T[size_]) {}

    ~Array() {
        delete[] data_;
    }

    int Size() const {
        return size_;
    }

 private:
    const int size_;
    T* data_;
};

template <>
class Array<bool> {
 public:
    explicit Array(int size)
        : size_(size),
          data_size_((size - 1) / 8 + 1),
          data_(new uint8_t[data_size_]) {}

    ~Array() {
        delete[] data_;
    }

    int Size() const {
        return size_;
    }

 private:
    const int size_;
    const int data_size_;
    uint8_t* data_;
};

この例では8個の bool 値を1個の uint8_t で扱って省メモリ化するために、 bool に対する完全特殊化を行っています。

部分特殊化

特定のテンプレート引数に対して 特殊化で使用する関数テンプレートやクラステンプレートを別のテンプレートに変更することができます。 これを部分特殊化といいます。

詳細は テンプレートの部分特殊化 - cppreference.com を参照してください。