...ing logging 4.0

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

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

(1) 似通った関数を関数テンプレートで一つにまとめよう

同じコードがあちこちに存在するプログラムは悪いコードの見本です.したがって,プログラミングで最も浅はかな行為はコピーアンドペーストです.これをやると必ずコードが重複しますからね.しかし,まったく同じではないが似たようなコードができてしまうことがあります.例えば,2つの引数のうち大きい方の整数を返す関数を書くとします。


001 int max(int a, int b)
002 {
003 if (a<b) return b;
004 else return a;
005 }

このようになるでしょう.ここで,int型ではなくdouble型バージョンも必要になったとします.int型バージョンをコピーアンドペーストして,intをdoubleに置き換えた次の関数も作りましょう.


001 double max(double a, double b)
002 {
003 if (a<b) return b;
004 else return a;
005 }

これでほとんど同じ2つの関数ができました.それではさらにchar型,float型,std::string型,その他の型のバージョンが必要になったとき,同じように関数を作ればいいのでしょうか.しかし,今作った2つの関数の違いは型だけで,関数内のアルゴリズムはまったく同じです.このような場合,テンプレートを使うことで,型を後から(関数を使うときに)指定することができるのでコピーアンドペーストは不要です.


001 template<typename T>
002 T max(T a, T b)
003 {
004 if (a<b) return b;
005 else return a;
006 }

これだけです.初めのtemplate<>は,この関数がテンプレートであることを意味します.typename Tというのは,後で指定した型に置き換えられる引数Tを定義していて,このTをテンプレート引数といいます.普通の関数の場合には引数に値が代入されることと同じで,テンプレート引数の場合には値ではなく型が代入されるということです.それではこの関数テンプレートを使ってみましょう.


001 #include <cstdio>
002
003 template<typename T>
004 T max(T a, T b)
005 {
006 if (a<b) return b;
007 else return a;
008 };
009
010 int main(int argc, char* argv[])
011 {
012 int a = max<int>(10, 20);
013 printf("%d\n", a);
014 return 0;
015 }

関数テンプレートのテンプレート引数Tに型を与えるためには,関数名と(の間に<型名>と書きます.上のサンプルの場合は,typename TのTにintを代入しているので,最初に自分で書いたint型バージョンのmax関数が自動的に作成されます.このとき,単純にTがintに置換されると考えるとよいです.テンプレートを理解する上で,この単純な置換が実は後でとても重要になってきます.

このように,double型バージョンが欲しくなったらmax<double>(...),char型ならmax<char>(...),float型ならmax<float>(...)など,関数を使うときに自由に型を指定することができます.これでコピーアンドペーストが不要になりましたね.

ところで,実は,型を指定しなくてもコンパイラがある程度推測してくれます.暗黙の型推論に成功する条件は,「すべての引数の型(関数の引数)からテンプレート引数の型が唯一に決定できる」です.従って,上のサンプルは次のように書き直せます.


012 int a = max(10, 20); // <int>は不要

10と20はどちらもint型なので,テンプレート引数Tはintだと判断してくれるわけです.同様に,10.0と20.0を与えれば,double型バージョンの関数が暗黙のうちに作成されます.

これまではテンプレート引数の数は1つだけでしたが,複数のテンプレート引数を持つ関数テンプレートを作ることもできます.


001 template<typename T, typename U>
002 void function(T first, U second)
003 {
004 ...
005 };
006
007 int main(int argc, char* argv[])
008 {
009 function<double,double>( 99.0, 77.0);
010 return 0;
011 }

このように単純にコンマで区切ってやるだけです.簡単ですね.

ところで,通常の引数にはデフォルト値を指定できますが,関数テンプレートのテンプレート引数には指定できません.


001 // 第2テンプレートパラメータにデフォルト引数を指定したつもり
002 template<typename T, typename U = int>
003 void function(T first, U second)
004 {
005 ...
006 };
007
008 int main(int argc, char* argv[])
009 {
010 // 第2テンプレート引数を省略した? --> コンパイルエラー!
011 function<double>( 99.0, 77.0);
012
013 return 0;
014 }

今回のまとめ:

1. 型以外が同じ関数はテンプレート化して一つにまとめよう.
2. すべての引数からテンプレート引数の型が決定できるときは暗黙の型推論が使える.
3. 関数テンプレートのテンプレート引数にはデフォルト引数を指定できない.

      • -

過去のエントリー:
C++テンプレートを使いこなす! 予告編