関数のオーバーロード
同じ名前の関数を複数定義することを、関数のオーバーロードという。これらの関数を区別するために、それらのパラメータは異なる必要がある。
次のコードには、info()
関数の複数のオーバーロードがあり、それぞれが異なる型のパラメータを受け取る。
すべての関数の名前はinfo()
だが、コンパイラは呼び出し時に使用される引数と一致する関数を選択する。例えば、リテラル1.2
は型double
であるため、double
を受け取る関数info()
が呼び出される。
呼び出す関数の選択はコンパイル時に行われるため、必ずしも簡単または明確であるとは限らない。例えば、int
はdouble
とreal
の両方に暗黙的に変換できるため、コンパイラは次のプログラムで呼び出す関数を決定できない。
注釈:関数本体がまったく同じ場合は、別々の関数を記述する必要は通常ない。テンプレートに関する章で、1つの定義を複数の型に使用する方法について後で説明する。
ただし、long
パラメータを取る別の関数オーバーロードがある場合、long
は double
やreal
よりもint
によりよく一致するため、曖昧さは解消される。
オーバーロードの解決
コンパイラは、引数に最もよく一致するオーバーロードを選択する。これをオーバーロード解決と呼ぶ。
オーバーロード解決は、ほとんどのケースでは単純で直感的だが、時には複雑になることもある。以下は、オーバーロード解決のルールだ。この本では、簡略化した形で説明している。
一致の状態は、最悪から最良の順に次の4つがある。
- 不一致
- 自動型変換による一致
const
修飾による一致- 完全一致
コンパイラは、オーバーロードの解決中に、関数のすべてのオーバーロードを考慮する。まず、すべてのオーバーロードのすべてのパラメータのマッチ状態を決定する。各オーバーロードについて、パラメータの中で最も低いマッチ状態が、そのオーバーロードのマッチ状態となる。
すべてのオーバーロードの一致状態が決定された後、最も一致するオーバーロードが選択される。最も一致するオーバーロードが複数存在する場合は、より複雑な解決ルールが適用される。本書ではこれらのルールの詳細には触れない。プログラムが複雑なオーバーロード解決ルールに依存している場合、プログラムの設計を変更する時期が来ている可能性がある。もう1つの選択肢は、テンプレートなど、Dの他の機能を利用することだ。さらに単純だが、必ずしも望ましいとは限らないアプローチは、型ごとに関数に異なる名前を付けることで、関数のオーバーロードを完全に放棄することである。例えば、sevenTimes_real()
やsevenTimes_double()
のようにする。
ユーザー定義型に対する関数のオーバーロード
関数のオーバーロードは、構造体やクラスでも有用だ。さらに、ユーザー定義型では、オーバーロードの解決における曖昧さがはるかに少なくなる。上記のinfo()
関数を、構造体の章で定義したいくつかの型に対してオーバーロードしよう。
このオーバーロードにより、TimeOfDay
オブジェクトをinfo()
とともに使用できるようになる。その結果、ユーザー定義型の変数は、基本型とまったく同じ方法で出力できるようになる。
TimeOfDay
オブジェクトは、info()
のオーバーロードと一致する:
07:00
以下は、Meeting
型に対するinfo()
のオーバーロードだ。
このオーバーロードは、TimeOfDay
の既定義のオーバーロードを利用していることに注意。これで、Meeting
オブジェクトも基本型とまったく同じ方法で出力できるようになった。
出力:
09:00-09:10 | "自転車ツアー"ミーティング(参加者3名) |
制限
上記のinfo()
関数のオーバーロードは大変便利だが、この方法にはいくつかの制限がある。
info()
は常にstdout
に出力される。任意のFile
に出力できるとより便利だろう。これを実現する1つの方法は、出力ストリームもパラメータとして渡すことだ。例えば、TimeOfDay
型の場合:これにより、
TimeOfDay
オブジェクトをstdout
を含む任意のファイルに出力できるようになる:注釈:特殊オブジェクト
stdin
、stdout
、およびstderr
は、File
型だ。- さらに重要なことは、
info()
は、変数の文字列表現を生成するというより一般的な問題を解決しないことだ。例えば、ユーザー定義型のオブジェクトをwriteln()
に渡す場合には、この関数は役に立たない。上記のコードは、プログラムで有用な形式ではなく、型の名前とそのメンバーの値を含む汎用的な形式でオブジェクトを出力する。
TimeOfDay(7, 0)
TimeOfDay
オブジェクトを、string
の特別な形式に変換する関数があれば、より便利だろう。"12:34"
のような特別な形式に変換する関数があったほうがはるかに便利だろう。構造体オブジェクトのstring
表現を定義する方法については、次の章で説明する。
演習
次の構造体についても、info()
関数をオーバーロードしよう。
Meal
には開始時刻しかないため、終了時刻を決定するために 1 時間半を追加する。構造体の章で先に定義したaddDuration()
関数を使用できる。
Meal
構造体オブジェクトの終了時刻がaddDuration()
で計算された後、DailyPlan
オブジェクトは次の出力のように表示されるべきだ:
10:30-11:45 | "自転車ツアー"ミーティング(参加者4名) |
12:30-14:00 | 昼食、場所: イスタンブール |
15:30-17:30 | "予算"ミーティング(参加者8名) |