可変長パラメータ
この章では、関数を呼び出す際のパラメータの柔軟性を高める2つのDの機能について説明する。
- デフォルト引数
- 可変長引数関数
デフォルト引数
関数パラメータの便利な機能として、デフォルト値を指定できることがある。これは、構造体メンバーのデフォルト初期値と似ている。
一部の関数のパラメータは、ほとんど同じ値で呼び出される。この例を見るために、string[string]
型の連想配列の要素を出力する関数を考えてみよう。この関数は、区切り文字もパラメータとして受け取るものとする。
この関数は、以下で ":"
をキー区切り文字として、 ", "
要素区切り文字として呼び出されている:
出力:
-- 色の辞書 --
青:mavi, 灰色:gri, 赤:kırmızı
セパレータがほとんどの場合この2つになる場合は、デフォルト値を定義しておくことができる。
デフォルト値を持つパラメータは、関数を呼び出すときに指定する必要はない。
必要に応じて、パラメータの値を指定することはできる。必ずしもすべて指定する必要はない。
出力:
-- 色の辞書 --
青=mavi, 灰色=gri, 赤=kırmızı
次の呼び出しでは、両方のパラメーターを指定している:
出力:
-- 色の辞書 --
青=mavi
灰色=gri
赤=kırmızı
デフォルト値は、パラメータリストの最後に位置するパラメータにのみ定義できる。
デフォルト引数としての特殊キーワード
次の特別なキーワードは、コード内の出現位置に対応する値を持つコンパイル時リテラルのように機能する。
-
__MODULE__
: モジュールの名前としてstring
-
__FILE__
: ソースファイル名としてstring
-
__FILE_FULL_PATH__
: ソースファイル名(完全なパスを含む)としてstring
-
__LINE__
: 行番号としてint
-
__FUNCTION__
: 関数の名前string
-
__PRETTY_FUNCTION__
: 関数の完全なシグネチャstring
これらはコードのどこでも使用できるが、デフォルト引数として使用すると動作が異なる。通常のコードで使用した場合、その値はコード内の出現場所を参照する。
報告された7行目は関数内にある。(訳注: 改変前のソースでは__LINE__は5行目にある)
ファイルdeneme.dの7行目、関数deneme.func 内で。
しかし、関数の定義がある場所ではなく、関数が呼び出されている行を特定したい場合もある。これらの特別なキーワードがデフォルト引数として指定されている場合、その値は関数が呼び出されている場所を参照する。
この場合、特別なキーワードは、関数の呼び出し元であるmain()
を指している。(訳注: 改変前のソースでは__LINE__は12行目にある)
ファイルdeneme.dの14行目にある関数deneme.mainから呼び出された。
上記に加えて、コンパイラや時刻に応じて値を取る、以下の特別なトークンもある。
-
__DATE__
: コンパイル日時をstring
-
__TIME__
: コンパイル時刻としてstring
-
__TIMESTAMP__
: コンパイル日時としてstring
-
__VENDOR__
: コンパイラベンダーとして (例:string
"Digital Mars D"
) -
__VERSION__
: コンパイラのバージョンをlong
で指定 (例: バージョン2.081の場合は値2081L
)
可変長引数関数
見た目とは違って、デフォルトのパラメータ値は関数が受け取るパラメータの数を変更しない。例えば、いくつかのパラメータにデフォルト値が割り当てられている場合でも、printAA()
は常に4つのパラメータを受け取り、実装に応じてそれらを使用する。
一方、可変長引数関数は、引数の数が指定されていない状態で呼び出すことができる。この機能は、writeln()
などの関数ですでに利用されている。writeln()
は、引数の数がいくつでも呼び出すことができる。
Dでは、可変長引数関数を4つの方法で定義することができる。
-
extern(C)
とマークされた関数でのみ機能する機能。この機能は、パラメータにアクセスするために使用される隠された_argptr
変数を定義する。この機能は安全ではないため、この本では説明しない。 - 通常のD関数で機能する機能。これも、隠された変数
_argptr
を使用するが、_arguments
変数も使用する。後者は、TypeInfo[]
型である。この機能は、まだ説明していないポインタに依存していることと、安全でない方法でも使用できることから、この本では説明しない。 - 指定されていない数のパラメータがすべて同じ型でなければならないという制限のある、安全な機能。このセクションでは、この機能について説明する。
- テンプレートパラメーターの数が指定されていない。この機能は、テンプレート章で後ほど説明される。
可変長引数関数の引数は、スライスとして関数に渡される。可変長引数関数は、特定の型のスライスを1つの引数として定義し、その直後に...
文字を続けて定義する。
この定義により、sum()
は可変長引数関数となり、double
、またはdouble
に暗黙的に変換できるその他の型であれば、引数の数を問わず受け取ることができる。
単一のスライスパラメータと...
文字は、すべての引数を表す。例えば、5つのdouble
値で関数が呼び出された場合、スライスは5つの要素を持つことになる。
実際、変数個のパラメーターは単一の切り出しとして渡すこともできる:
可変長引数関数には、パラメータリストで最初に定義しなければならない必須のパラメータも指定できる。例えば、次の関数は、括弧内に指定された数のパラメータを、その数に関係なく出力する。この関数は、要素の数を柔軟に指定できるが、括弧は常に指定する必要がある。
最初の2つのパラメーターは必須である:
必須パラメーターが指定されている限り、残りはオプションだ:
出力:
{apple}{pear}{banana}
可変長引数関数の引数の有効期間は短い
可変長引数に対して自動的に生成されるスライス引数は、寿命の短い一時的な配列を指す。関数がその実行中にのみ引数を使用する場合、この事実は問題にはならない。しかし、関数が後で使用するためにそれらの要素へのスライスを保持すると、バグになる。
独立関数foo()
とメンバー関数S.foo()
は、プログラムスタック上に存在する自動的に生成された一時配列へのスライスを保存しているため、どちらもエラーになる。これらの配列は、可変長引数関数の実行中にのみ有効である。
そのため、関数が可変長引数の要素のスライスを格納する必要がある場合は、まずそれらの要素のコピーを取得する必要がある。
ただし、可変長引数関数は、適切な配列のスライスでも呼び出すことができるため、その場合は要素のコピーは不要になる。
正しく効率的な解決策は、同じ名前で、1つは可変長引数を受け取り、もう1つは適切なスライスを受け取る2つの関数を定義することだ。呼び出し側が可変長の引数を渡した場合、可変長引数を受け取る関数が呼び出され、呼び出し側が適切なスライスを渡した場合、適切なスライスを受け取る関数が呼び出される。
同じ名前で異なるパラメータを持つ複数の関数を定義することを、関数のオーバーロードと呼ぶ。これは次の章で説明する。
演習
次のenum
が既に定義されていると仮定する:
また、操作とその2つのオペランドの計算を表すstruct
が存在すると仮定する:
例えば、オブジェクトCalculation(Operation.divide, 7.7, 8.8)
は、7.7を8.8で割った結果を表す。
これらのstruct
オブジェクトを不特定の数受け取り、各Calculation
の結果を計算し、その結果をすべてdouble[]
型のスライスとして返す関数を設計しよう。
例えば、次のコードのように関数を呼び出すことができるようにする必要がある。
コードの出力は次のようなものになるはずだ:
[3.3, -1.1, 36.3, 0.875]