のつづきです。
template の基礎
template とは何でしょうか?
「コードを書いたら、あとから型や値を差し替えることのできるひな型」
とでもいいでしょうか。
このひな型は
- 関数テンプレート(関数のひな型)
- クラステンプレート(クラスのひな型)
- 変数テンプレート(変数のひな型)C++14以降
- エイリアステンプレート(usingのひな型)C++11以降
の4種類があります。
しかし、本稿では変数テンプレートおよびエイリアステンプレートについては触れません。
{関数/クラス}テンプレートはつい、テンプレート{関数/クラス}と言ってしまいがちですが、ひな型を指す場合には関数テンプレートおよびクラステンプレートが正しい名前です。
関数テンプレートは以下のようなものです。
// どんな型(T)でも入れ替える「ひな型」
template <typename T>
void swap_anything(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
// 使うとき
int x = 10, y = 20;
swap_anything(x, y); // int 用の関数が自動で作られる
std::string s1 = "Apple", s2 = "Orange";
swap_anything(s1, s2); // string 用の関数が自動で作られる
クラステンプレートはこんなのです。
// どんな型(T)でも1つだけ保管できる「ケース」のひな型
template <typename T>
class Storage {
private:
T item;
public:
Storage(T data) : item(data) {}
void print() {
std::cout << "中身は: " << item << std::endl;
}
};
// 使うとき
Storage<int> my_int(123); // 整数を入れるケース
Storage<std::string> my_str("Hello"); // 文字列を入れるケース
my_int.print();
my_str.print();
どちらの例も int と std::string 同じロジックで扱うことができています。
実務プログラムの世界では型が違うにもかかわらず、同じロジックで扱う必要があるという場面がとても多いです。テンプレートを書くとそれらをいちいち書かなくてもよくなります。
テンプレートパラメータ
C++20以降のテンプレートパラメータには
- 型
- 非型(整数、列挙型、ポインタ、浮動小数点、リテラル型クラス)
- テンプレート
を与えることができます。
もう、ここでひっかかる点だらけですね。
テンプレートパラメータって型と整数タイプの非型、そしてテンプレートだけじゃないの?
なに?ポインタ?浮動小数点?そしてリテラル型クラスという謎の型?
一つ一つ行きましょう。
テンプレートパラメータとしてのポインタ
ポインタはご存知ですよね。int* という型などです。テンプレートパラメータにはメンバ(メンバ関数)へのポインタも含まれます。
なんか解せない表情をされていますね。
「ポインタの値って実行時にならないと決まらないんじゃないの?それをどうやってテンプレートパラメータに与えるの?」
と顔に書いてあります。
その通りなんですが、コンパイル時に決まるものもあるでしょう?
- 関数ポインタ
- 静的変数のアドレス(グローバル変数、静的な内部リンケージ変数、static データメンバ)
- メンバ(データ/関数)へのポインタ
- 文字列リテラル
などです。これらはテンプレートパラメータにできますが、
これ以外のポインタはテンプレートパラメータには出来ません。
正直なところ、ポインタ型はテンプレートパラメータにしなくても実行時に与えることができれば不自由しないと思われるかもしれません。
そう、不自由はしません。ではなぜポインタをテンプレートにしたいのでしょうか?
その最大の理由は『ゼロコスト抽象化』です。
例えば、メンバポインタをテンプレートに与えると、コンパイラは『クラスの先頭から何バイト目を読み書きするか』を事前に知ることができます。
するとストレートにメンバオフセットの定数を直接つかったオブジェクトコードが作られます。
テンプレートパラメータに関数ポインタを渡す場合も、その関数はインライン展開されるかもしれません。インライン化されるとさらに様々な最適化による恩恵が得られるかもしれません。
実行時にポインタを渡すと『柔軟だが少し遅い』。 テンプレートでポインタを渡すと『柔軟で、かつ最高に速い』。 この『速さ』と『汎用性』の両立こそが、ポインタをテンプレートパラメータにする真の価値です。
テンプレートパラメータとしての浮動小数点
浮動小数点数の定数をテンプレートに与えると、たとえば以下のように
template <double Scale>
struct Unit {
double value;
constexpr double get_scaled() const { return value * Scale; }
};
// ミリメートル(0.001)とメートル(1.0)を「型」として区別
using Meters = Unit<1.0>;
using Millimeters = Unit<0.001>;
void process(Meters m) { /* ... */ }
int main() {
process(Meters{5.0});
// process(Millimeters{5000.0}); // コンパイルエラー!型の安全性が守られる
}
係数の違いを別の型として区別することが出来ます。
あるいは、特定の値のときだけ最適な演算方法を選択したい場合などは、コンパイル時分岐や、テンプレート特殊化によって処理の選択をコンパイル時に行い、実行時のオーバーヘッドを減らせるかもしれません。
(つづく)