特殊化¶
テンプレートでは実引数に応じて関数やクラスを生成します。
// 関数テンプレート
template <typename T>
T Sum(T a, T b) {
return a + b;
}
// 関数テンプレートの関数の呼び出し
Sum<int>(1, 2);
Sum<int>(1, 2)
という関数テンプレートの関数の呼び出しによって
T
が int
である関数が必要と判断され、次の関数が生成されます。
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;
}
テンプレートの明示的インスタンス化とextern template
ヘッダーファイルなどのテンプレートを使用する箇所で関数やクラスを生成すると、 コンパイル速度が低下してしまいます。 ヘッダファイルでは宣言だけ行い、ソースファイルで明示的に関数やクラスを生成することで 予めtemplateを実体化できるのでコンパイル速度が向上します。
#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)
のように関数テンプレートの関数を呼び出すとリンクエラーになります。
かといって明示的なインスタンス化を増やしていくと、それを共有ライブラリにすることを考えた場合、
ライブラリサイズが肥大化してしまいます。
こうした問題を避けるためには、extern templateを利用します。 通常通りヘッダファイルでtemplate関数/クラスの定義を行うのですが 実体化する頻度が高いものだけをexternし、ソースファイルで実体化させます
#ifndef SUM_H_
#define SUM_H_
// 関数テンプレートの定義
template <typename T>
T Sum(T a, T b) {
return a + b;
}
//暗黙的実体化を阻止
extern template int Sum<int>(int, int);
#endif // SUM_H_
#include "sum.h"
//よく使うものだけ実体化させる
template int Sum<int>(int, int);
完全特殊化¶
関数テンプレートやクラステンプレートでは、すべてのパラメータが確定した時に別の定義を書くことができます。 これによって特定のテンプレート引数に対する挙動を変更することができます。 これを完全特殊化 (または明示的特殊化) といいます。
関数テンプレートの完全特殊化は次のようにします。
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
を参照してください。
関数テンプレートを部分特殊化したいとき
部分特殊化はクラステンプレートに対してのみ行なえます。しかし関数テンプレートに対しても行いたいことがあります。
その場合はSFINAE(Substitution Failure Is Not An Error)を利用した書き方をします
template <bool cond, typename T>
using enable_if_t = typename std::enable_if<cond, T>::type;
template <typename T, enable_if_t<(なんか条件式), std::nullptr_t> = nullptr>
void foo(T t) {}
template <typename T, enable_if_t<!(なんか条件式), std::nullptr_t> = nullptr>
void foo(T t) {}
std::enable_ifを使ってオーバーロードする時、enablerを使う? - Qiita
あるいはC++17で追加されたconstexpr if 文を利用することができます。