...ing logging 4.0

はてなブログに移行しました。D言語の話とかいろいろ。

C++テンプレートを使いこなす! 第一部 (3)

(3) 特定の型だけ別に扱いたいときは特殊化しよう

まずはおさらいです.関数テンプレートを使えば,型だけが異なる関数を複数作らなくても済むので,コードの重複を避けられるのでした.

ところが,「普段は汎用の関数テンプレートで十分だけど,この型の場合だけは専用の処理をしたい!」という場合があります.例えば,汎用の関数テンプレートでは必要な性能が出ないため,特定の型に限り特化したコードが必要になることがあります.

ここでは,それとは別の例を考えることにします.まず,関数テンプレートの中で組み込みの数値型( int,double,float など)を受け取って二乗した値を返す square<> 関数テンプレートを作ったとします.


001 #include <cstdio>
002
003 // 値を二乗する汎用の関数テンプレート
004 template<typename T> T square(T t)
005 {
006 return t * t;
007 };
008
009 void main()
010 {
011 // 汎用の関数テンプレートを使用
012 printf("square<int> : 2^2 = %d\n", square(2));
013 printf("square<double> : 0.5^2 = %f\n", square(0.5));
014 }

これまでのコードが理解できていれば簡単ですね. square(2) と square(0.5) に型指定がないのは,この場合は省略できるからでした.

それでは,次に複素数を表す Complex 構造体をユーザが作成したとします.さて,複素数を二乗した値を計算できるでしょうか.


001 #include <cstdio>
002
003 // 値を二乗する汎用の関数テンプレート
004 template<typename T> T square(T t)
005 {
006 return t * t;
007 };
008
009 // 複素数を表す型
010 struct Complex
011 {
012 double r;// 実数項
013 double i;// 虚数
014 };
015
016 void main()
017 {
018 // 汎用の関数テンプレートを使用
019 Complex a, b;
020 a.r = 3;
021 a.i = 2;
022 b = square(a); // square<> 関数に Complex 構造体は使えません!
023 printf("square<Complex> : (%f+%fi)^2 = %f+%fi\n", a.r, a.i, b.r, b.i);
024 }

残念ながら,このコードはコンパイルできません.なぜなら, square<> 関数テンプレートの定義をよく見ると,関数の引数 t に対して * 演算子を使っています.従って,square<> 関数テンプレートは,テンプレートパラメータ T に対して * 演算子が使える型しか受け取ることができないのです.そのため,コンパイラは「 Complex 構造体には * 演算子が使えないよ」とエラーを出すのです.

さて,それでは諦めるしかないのでしょうか.いえ,これはテンプレートの特殊化が一つの解決策になります(本当はoperator*のオーバーロードの方が適していると思いますけどね).


001 #include <cstdio>
002
003 // 値を二乗する汎用の関数テンプレート
004 template<typename T> T square(T t)
005 {
006 return t * t;
007 };
008
009 // 複素数を表す型
010 struct Complex
011 {
012 double r;// 実数項
013 double i;// 虚数
014 };
015
016 // Complexで特殊化
017 template<> Complex square<Complex>(Complex t)
018 {
019 Complex ret;
020 ret.r = t.r * t.r - t.i * t.i;
021 ret.i = 2 * (t.r * t.i);
022 return ret;
023 };
024
025 void main()
026 {
027 // 汎用の関数テンプレートを使用
028 printf("square<int> : 2^2 = %d\n", square(2));
029 printf("square<double> : 0.5^2 = %f\n", square(0.5));
030
031 // Complexで特殊化した関数テンプレートを使用
032 Complex a, b;
033 a.r = 3;
034 a.i = 2;
035 b = square(a);
036 printf("square<Complex> : (%f+%fi)^2 = %f+%fi\n", a.r, a.i, b.r, b.i);
037 }

これまで,任意の型 any のための square<> 関数を呼び出すときは square<any> のように書いていました.同じように,特殊化した関数テンプレートを定義するときには,やはり関数名を square<any> と書きます.そして,最初の template<typename T> は,特定の型に固定するので(パラメータとして存在する意味がないと考えられる) template<> と書き,テンプレートパラメータ T の部分をすべて any と書き換えます.今は any が具体的に Complex ですから,次のようになります.


template<typename T> T square(T t) { ... };
template<> Complex square<Complex>(Complex t) { ... };

これで,square<> 関数テンプレートは,与えられた型が Complex ならば square<Complex> の方が呼び出され,それ以外ならば汎用の方が呼び出されるようになります.

関数テンプレートの特殊化で注意することは,先に汎用の関数テンプレート square<T> を定義してから,特殊化した square<Complex> を定義することです.これを逆にするとコンパイルエラーになります.

ところで,関数テンプレートの宣言はもっと省略できます.実は,関数名の後の型名は引数などから推測できるためなくても支障がないので,


template<> Complex square<Complex>(Complex t)


template<> Complex square(Complex t)

と書くことができます.少しだけですがシンプルになりました.

では,今回のまとめです.

1. 特定の型専用のテンプレートが必要なら特殊化しよう.
2. 汎用の関数テンプレートを先に,特殊化版を後に定義しよう.
3. 関数テンプレートの定義では関数名の直後のテンプレートパラメータを省略できる.

      • -

過去の関連エントリー
C++テンプレートを使いこなす! 予告編
C++テンプレートを使いこなす! 第一部 (1)
C++テンプレートを使いこなす! 第一部 (2)