関数
FunctionBody: BlockStatement BodyStatement InStatement BodyStatement OutStatement BodyStatement InStatement OutStatement BodyStatement OutStatement InStatement BodyStatement InStatement: in BlockStatement OutStatement: out BlockStatement out ( Identifier ) BlockStatement BodyStatement: body BlockStatement
関数の返値
関数の返値は右辺値とみなされます。 つまり、返値を他の関数に参照渡しすることはできません。
仮想関数
仮想関数とは、 直接呼び出されるのではなく、 vtbl[] と呼ばれるポインタテーブル経由で間接的に呼び出される関数のことをいいます。 全ての非staticかつ非privateかつ非templateなメンバ関数は、 仮想関数です。 これは非効率的に聞こえるかもしれませんが、 コード生成時にDはクラス階層を全て把握していますので、 オーバーライドされていない関数への呼び出しは全て最適化されて non-virtual になります。 C++プログラマは "疑わしきはvirtualにせよ" でコードを書く傾向にありますが、 Dの流儀である "non-virtualにできると証明できない限りvirtualにせよ" の方が、結果的には多くの関数を直接呼び出せます。 オーバーライドしたい関数を 仮想関数にし忘れる、 というバグを減らすことにもつながります。
Dのリンケージを持たない関数はvirtualにはできず、 それゆえオーバーライドできません。
メンバテンプレート関数はvirtualにはできず、 それゆえオーバーライドできません。
final という印のついた関数は、 private でない限り、 派生クラスでオーバーライドできません。例えば:
class A { int def() { ... } final int foo() { ... } final private int bar() { ... } private int abc() { ... } } class B : A { int def() { ... } // ok。A.defをオーバーライドする。 int foo() { ... } // エラー。A.fooはfinal int bar() { ... } // ok。A.bar は final private だが virtual でない int abc() { ... } // ok。A.abc は virtual でない。B.abc は virtual } void test(A a) { a.def(); // B.def を呼ぶ a.foo(); // A.foo を呼ぶ a.bar(); // A.bar を呼ぶ a.abc(); // A.abc を呼ぶ } void func() { B b = new B(); test(b); }
共変の戻り値型がサポートされています。 すなわち、派生クラスでオーバーライドした関数では、 基底クラスでの返値型の 派生型オブジェクトを返すことができます。
class A { } class B : A { } class Foo { A test() { return null; } } class Bar : Foo { B test() { return null; } // Foo.test() と共変なオーバーライド }
仮想関数は全て隠し引数として this 参照を受け取り、 これは関数が呼ばれた対象オブジェクトを指しています。
関数の継承とオーバーライド
派生クラスの関数で、基底クラスの関数と名前と引数の同じものは、 基底の関数を上書きします:class A { int foo(int x) { ... } } class B : A { override int foo(int x) { ... } } void test() { B b = new B(); bar(b); } void bar(A a) { a.foo(1); // B.foo(int) を呼ぶ }
しかしながら、オーバーロードの解決の際には、 基底クラスのメンバ関数は考慮されません。
class A { int foo(int x) { ... } int foo(long y) { ... } } class B : A { override int foo(long x) { ... } } void test() { B b = new B(); b.foo(1); // B.foo(long) を呼ぶ。A.foo(int) は考慮しない A a = b; a.foo(1); // A.foo(int) を呼ぶ }
オーバーロードの解決の課程で基底クラスの関数も考慮に入れたい場合は、 AliasDeclaration を使います:
class A { int foo(int x) { ... } int foo(long y) { ... } } class B : A { alias A.foo foo; override int foo(long x) { ... } } void test() { B b = new B(); bar(b); } void bar(A a) { a.foo(1); // A.foo(int) を呼ぶ B b = new B(); b.foo(1); // A.foo(int) を呼ぶ }
関数のデフォルト引数値は継承されません:
class A { void foo(int x = 5) { ... } } class B : A { void foo(int x = 7) { ... } } class C : B { void foo(int x) { ... } } void test() { A a = new A(); a.foo(); // A.foo(5) を呼ぶ B b = new B(); b.foo(); // B.foo(7) を呼ぶ C c = new C(); c.foo(); // エラー、C.fooには引数が必要 }
インライン関数
inlineという予約語はありません。 関数をインライン化するかどうかは、 コンパイラが決定します。Cでの予約語registerに対し、 実際に変数をレジスタに配置するかどうかはコンパイラにゆだねられているのと 同様です。(Dにはregisterという予約語もありません。)関数オーバーロード
関数は、仮引数の型と実引数の型がどれだけよくマッチするかという基準によって オーバーロードされます。 もっともよく マッチする関数が選択されます。 マッチの適合度は以下の3段階あります:
- マッチしない
- 暗黙変換によるマッチ
- 正確なマッチ
実引数それぞれ (this ポインタも含む) が 関数の対応する仮引数と比較され、 その実引数に関するマッチ適合度が決定されます。 引数のうち 一番悪いもののマッチ適合度が、 関数全体としてのマッチ適合度と定義されます。
複数の関数が同じマッチ適合度にある場合、 曖昧なためエラーとなります。
D のリンケージを持たない関数はオーバーロードできません。 名前マングリングが引数の型を考慮していないためです。
関数の引数
関数のパラメータには3種類、in, out, ref, lazy があって、デフォルトは in です。 out と ref は記憶域クラスのようにはたらきます。例えば:int foo(int x, out int y, ref int z, int q);x は in で、y が out、z は ref で q が in です。
out を使うのはかなりまれで、 ref は更にまれなので、 これらにはキーワードが必要にして、デフォルトを in にしました。
- 関数宣言が、どれが入力でどれが出力か明示することで、 理解しやすくなる。
- IDLを別の言語として用意する必要がなくなる。
- コンパイラに多くの情報を与えることで、 よりよいエラーチェックやコード生成が 可能になる。
out パラメタは、その型のデフォルト初期化子にセットされます。 例えば:
void foo(out int x) { // x は foo() の最初で 0 にセットされる } int a = 3; foo(a); // a は 0 になる void abc(out int x) { x = 2; } int y = 3; abc(y); // y は 2 になる void def(ref int x) { x += 1; } int z = 3; def(z); // z は 4 になる
動的配列やオブジェクト引数は元々参照渡しされますが、 これらについては、 in/out/ref は参照に対して適用され、中身には影響しません。
Lazy 引数は関数が呼び出されるときに評価されるのではなく、 関数の中で引数が使われる時点で初めて評価されます。このため、 lazy引数は一度も実行されないこともあれば複数回実行される可能性もあります。 lazy引数は左辺値とすることはできません。
void dotimes(int n, lazy void exp) { while (n--) exp(); } void test() { int x; dotimes(3, writefln(x++)); }
次のように表示されます:
0 1 2
void 型のlazy引数は、 任意の型の引数を受け付けます。
デフォルト引数
関数のパラメタ宣言にはデフォルト値を設定することが可能です:
void foo(int x, int y = 3) { ... } ... foo(4); // foo(4, 3); と同じ
デフォルト引数は、 関数宣言のコンテキストで評価されます。 引数にデフォルト値を設定する場合、 それよりも後ろの引数にも全てデフォルト値を設定する必要があります。
可変個引数
引数の個数が定まっていな関数のことを、可変個引数関数と呼びます。 可変個引数関数には、 以下の3つの形式があります:- Cスタイル可変個引数関数
- Dスタイル可変個引数関数
- 型安全可変個引数関数
Cスタイル可変個引数関数
Cスタイル可変個引数関数は、必須の引数の後ろに ... をつけた形で宣言されます。 extern (C) のような、D以外のリンケージを持ちます:extern (C) int foo(int x, int y, ...); foo(3, 4); // ok foo(3, 4, 6.8); // ok, 可変引数が1つ。 foo(2); // エラー。引数yは必須。この形式の関数は、最低1つの非変引数を受け取る必要があります。
extern (C) int def(...); // エラー、最低一つの引数が必要Cスタイル可変個引数関数は、Cの可変個引数関数の呼び出し規約と一致していて、 printf のような Cのライブラリ関数を呼び出すのに利用できます。 可変個引数関数の中では、 特別なローカル変数 _argptr が使えます。 この変数は、可変引数の1つ目を指す void* 型のポインタです。 引数にアクセスするには、_argptr を、 期待される実際の型へのポインタにキャストします:
foo(3, 4, 5); // 可変個引数の先頭は、5 int foo(int x, int y, ...) { int z; z = *cast(int*)_argptr; // z に 5 が入る }CPUアーキテクチャの違いによってスタックのメモリ配置が異なる問題を 考慮にいれるならば、可変個引数のアクセスには std.c.stdarg を使います:
import std.c.stdarg;
Dスタイル可変個引数関数
型情報付き可変個引数関数は、必須の引数の後ろに ... をつけた形で宣言します。 この種類の関数はDリンケージを持ち、 固定の引数が一個もなくても構いません:int abc(char c, ...); // 必須引数が一つ: c int def(...); // ok可変個引数関数の中では、 特別なローカル変数 _argptr が使えます。 この変数は、可変引数の1つ目を指す void* 型のポインタです。 引数にアクセスするには、_argptr を、 期待される実際の型へのポインタにキャストします:
foo(3, 4, 5); // 可変個引数の先頭は、5 int foo(int x, int y, ...) { int z; z = *cast(int*)_argptr; // z に 5 が入る }Dのリンケージを持つ可変個引数関数には、さらに TypeInfo[] 型の隠れた引数 _arguments が渡されます。 _arguments によって、 実際に渡された引数の個数とそれぞれ型がわかります。 この情報を元に、型安全な可変個引数関数を作ることができます。
import std.stdio; class Foo { int x = 3; } class Bar { long y = 4; } void printargs(int x, ...) { writefln("%d arguments", _arguments.length); for (int i = 0; i < _arguments.length; i++) { _arguments[i].print(); if (_arguments[i] == typeid(int)) { int j = *cast(int *)_argptr; _argptr += int.sizeof; writefln("\t%d", j); } else if (_arguments[i] == typeid(long)) { long j = *cast(long *)_argptr; _argptr += long.sizeof; writefln("\t%d", j); } else if (_arguments[i] == typeid(double)) { double d = *cast(double *)_argptr; _argptr += double.sizeof; writefln("\t%g", d); } else if (_arguments[i] == typeid(Foo)) { Foo f = *cast(Foo*)_argptr; _argptr += Foo.sizeof; writefln("\t%X", f); } else if (_arguments[i] == typeid(Bar)) { Bar b = *cast(Bar*)_argptr; _argptr += Bar.sizeof; writefln("\t%X", b); } else assert(0); } } void main() { Foo f = new Foo(); Bar b = new Bar(); writefln("%X", f); printargs(1, 2, 3L, 4.5, f, b); }これは次のように表示されます:
00870FE0 5 arguments int 2 long 3 double 4.5 Foo 00870FE0 Bar 00870FD0CPUアーキテクチャの違いによってスタックのメモリ配置が異なる問題を 考慮にいれるならば、可変個引数のアクセスには std.stdarg を使います:
import std.stdio; import std.stdarg; void foo(int x, ...) { writefln("%d arguments", _arguments.length); for (int i = 0; i < _arguments.length; i++) { _arguments[i].print(); if (_arguments[i] == typeid(int)) { int j = va_arg!(int)(_argptr); writefln("\t%d", j); } else if (_arguments[i] == typeid(long)) { long j = va_arg!(long)(_argptr); writefln("\t%d", j); } else if (_arguments[i] == typeid(double)) { double d = va_arg!(double)(_argptr); writefln("\t%g", d); } else if (_arguments[i] == typeid(FOO)) { FOO f = va_arg!(FOO)(_argptr); writefln("\t%X", f); } else assert(0); } }
型安全可変個引数関数
型安全可変個引数関数は、 引数の可変部分が配列やクラスオブジェクトの生成に使われる、 という形で実現されています。配列の場合:
int test() { return sum(1, 2, 3) + sum(); // 6+0 を返す } int func() { int[3] ii = [4, 5, 6]; return sum(ii); // 15を返す } int sum(int[] ar ...) { int s; foreach (int x; ar) s += x; return s; }静的配列の場合:
int test() { return sum(2, 3); // エラー。配列に3個の値が必要 return sum(1, 2, 3); // 6を返す } int func() { int[3] ii = [4, 5, 6]; int[] jj = ii; return sum(ii); // 15を返す return sum(jj); // エラー、型の不一致 } int sum(int[3] ar ...) { int s; foreach (int x; ar) s += x; return s; }クラスオブジェクトの場合:
class Foo { int x; char[] s; this(int x, char[] s) { this.x = x; this.s = s; } } void test(int x, Foo f ...); ... Foo g = new Foo(3, "abc"); test(1, g); // ok. gはFooのインスタンス test(1, 4, "def"); // ok test(1, 5); // エラー、Fooのコンストラクタでマッチするものがない実装は、オブジェクトや配列のインスタンスをスタック上に作ることができます。 ということは、可変個引数関数からreturnした後にそのインスタンスを参照すると、 エラーになります:
Foo test(Foo f ...) { return f; // エラー。インスタンスfの内容はreturnのあと無効になる } int[] test(int[] a ...) { return a; // エラー。returnの後は配列の内容は無効 return a[0..1]; // エラー。returnの後は配列の内容は無効 return a.dup; // ok, コピーが作られている }その他の型の場合、引数はその型そのものになります:
int test(int i ...) { return i; } ... test(3); // 3を返す test(3, 4); // エラー、引数が多すぎる int[] x; test(x); // エラー。型の不一致
遅延可変個引数
可変個引数の型が、 引数を持たないdelegateの配列であった場合:
void foo(int delegate()[] dgs ...);
delegate型でないような実引数は、 その型のdelegateへ暗黙変換されます。
int delegate[] dg; foo(1, 3+x, dg, cast(int delegate[])null);
これは以下と同じです:
foo( { return 1; }, { return 3+x; }, dg, null );
ローカル変数
ローカル変数に値を代入する前に使用するのはエラーです。実装は、 このエラーを必ずしも検出しないかもしれません。 他の言語のコンパイラはしばしばこの問題に対して警告を出しますが、 これは常にバグなので、エラーであるべきです。
一度も参照されないローカル変数を宣言するのもエラーです。 古い利用されてないコードに現れるような死んだ変数は、 メンテナンスプログラマの混乱の元でしかありません。
同じ関数内の他の変数の名前を隠すローカル変数は、 エラーです:
void func(int x) { int x; // エラー, xの前の定義を隠している double y; ... { char y; // エラー, yの前の定義を隠している int z; } { wchar z; // 問題なし, zの前の定義はスコープ外 } }
これは不合理に見えるかもしれませんが、実際上は、 このエラーに該当するのはバグのあるコードか、 少なくともバグのように見えるコードです。
ローカル変数のアドレスや参照を返すのは、 エラーです。
ローカル変数とラベルとに同じ名前を付けるのはエラーです。
ネストした関数
他の関数の内部にネストして関数を書くことができます:
int bar(int a) { int foo(int b) { int abc() { return 1; } return b + abc(); } return foo(a); } void test() { int i = bar(3); // i には 4 が代入される }
ネスト関数は、その名前がスコープにある範囲からのみ呼び出せます。
void foo() { void A() { B(); // エラー。Bは前方参照 C(); // エラー。Cは未定義 } void B() { A(); // ok, スコープ内 void C() { void D() { A(); // ok B(); // ok C(); // ok D(); // ok } } } A(); // ok B(); // ok C(); // エラー。C は未定義 }
そして:
int bar(int a) { int foo(int b) { return b + 1; } int abc(int b) { return foo(b); } // ok return foo(a); } void test() { int i = bar(3); // ok int j = bar.foo(3); // エラー。bar.foo はここから見えない }
外側の関数の変数や、 その他のシンボルにアクセスできます。 読み書き双方のアクセスが可能です。
int bar(int a) { int c = 3; int foo(int b) { b += c; // 4 が b に足される c++; // bar.c は 5 になる return b + c; // 返値は 12 } c = 4; int i = foo(a); // i は 12 return i + c; // 17 を返す } void test() { int i = bar(3); // i に 17 が代入される }
このアクセスは、ネストの階層が複数段でも構いません:
int bar(int a) { int c = 3; int foo(int b) { int abc() { return c; // bar.c にアクセス } return b + c + abc(); } return foo(3); }
staticなネストした関数は、外側の関数のスタック変数を触ることができず、 static変数だけにアクセスできます。 これはstaticメンバ関数のふるまいとよく似ています。
int bar(int a) { int c; static int d; static int foo(int b) { b = d; // ok b = c; //エラー。foo() は bar() のスタックフレームにアクセスできない return b + 1; } return foo(a); }
関数は、メンバ関数の中にもネストできます:
struct Foo { int a; int bar() { int c; int foo() { return c + a; } return 0; } }
メンバ関数の中にネストしたクラスや構造体からは、 外側の関数のスタック変数にはアクセスできませんが、 その他のシンボルにはアクセス可能です:
void test() { int j; static int s; struct Foo { int a; int bar() { int c = s; // ok, s は static int d = j; // エラー。test() のスタックフレームにはアクセス不可 int foo() { int e = s; // ok, s は static int f = j; // エラー。test() のスタックフレームにはアクセス不可 return c + a; // ok, bar() のフレームにはアクセス可能 // 従って、Foo.bar() の 'this' ポインタを通して // Fooのメンバにもアクセスできる } return 0; } } }
ネストした関数は、常にDの関数リンケージを持ちます。
モジュールレベルの定義と違い、関数スコープ内での宣言は、 書かれた順番に処理されます。どういうことかと言うと、 2つのネストした関数が相互に呼び出し合うことはできません:
void test() { void foo() { bar(); } // error, bar は未定義 void bar() { foo(); } // ok }
これはdelegateを使って解決します:
void test() { void delegate() fp; void foo() { fp(); } void bar() { foo(); } fp = &bar; }
今後の方向性: この制限はおそらく緩和されます。
delegate, 関数ポインタ, 動的クロージャ
関数ポインタは、staticなネストした関数を指すことができます:
int function() fp; void test() { static int a = 7; static int foo() { return a + 3; } fp = &foo; } void bar() { test(); int i = fp(); // i は 10 となる }
delegate では、staticでないネストした関数を指せます:
int delegate() dg; void test() { int a = 7; int foo() { return a + 3; } dg = &foo; int i = dg(); // i is set to 10 }
しかしながらスタック変数は、 宣言されている関数が終了すると無効になります。 スタック変数へのポインタが、関数終了後には無効になるのと同様です:
int* bar() { int b; test(); int i = dg(); // エラー, test.a はもはや存在しない return &b; // エラー, bar.b は bar() の終了後には有効でない }
非staticなネストした関数へのdelegateは、 二つのデータを持ちます。 それは、外側の関数のスタックフレームを指すポインタ(フレームポインタ と呼びます)と、関数のアドレスです。 これは、構造体/クラスの 非staticメンバ関数へのdelgateが this ポインタとメンバ関数のアドレスから なっていることと似ています。どちらのタイプのdelegateも互換性があり、 実際に、同じ型です:
struct Foo { int a = 7; int bar() { return a; } } int foo(int delegate() dg) { return dg() + 1; } void test() { int x = 27; int abc() { return x; } Foo f; int i; i = foo(&abc); // i は 28 になる i = foo(&f.bar); // i は 8 になる }
環境と関数の組み合わせは、 動的クロージャ と呼ばれています。
デリゲートの .ptr プロパティは、 void* 型でフレームポインタの値を返します
デリゲートの .funcptr プロパティは、 function型で関数ポインタの値を返します
今後の方向性: 関数ポインタとdelegateの構文を共通にして、 相互に互換性を持たせる予定です。
無名関数・無名デリゲート
関数リテラル を参照してください。
main() 関数
コンソールプログラムでは、main() が実行開始点となります。 全てのモジュール初期化子が呼ばれ、 ユニットテストが実行された後に呼び出されます。 mainから戻った後は、全てのモジュールデストラクタが呼び出されます。 main() の宣言は必ず以下のいずれかとします:
void main() { ... } void main(char[][] args) { ... } int main() { ... } int main(char[][] args) { ... }
コンパイル時関数実行 (CTFE)
関数の一部は、コンパイル時に実行可能です。 再帰やループが必要なアルゴリズムで生成される定数をたたみ込みたい時に、 この機能は有効です。 コンパイル時に実行するためには、 関数は以下の条件を満たしている必要があります:
- 実引数は以下のいずれか:
- 整数リテラル
- 浮動小数点数リテラル
- 文字リテラル
- 文字列リテラル
- このリストに含まれている項目からなる 配列リテラル
- このリストに含まれている項目からなる 連想配列リテラル
- このリストに含まれている項目からなる 構造体リテラル
- このリストに含まれている項目で初期化された const変数
- デリゲート
- 関数ポインタ
- デリゲートリテラル
- 関数リテラル
- 関数の引数を C形式の可変長引数にすることはできない
- synchronized関数であってはならない
- 関数本体の式に、以下があってはならない:
- 例外のthrow
- ポインタ、クラスの使用
- グローバルの状態や変数の参照
- ローカルstatic変数の参照
- delete
- コンパイル時実行不可能な関数の 呼び出し
- 関数本体の文に、以下があってはならない:
- synchronized 文
- throw 文
- with 文
- scope 文
- try-catch-finally 文
- ラベル付きbreak,continue文
- 特別な場合として、
以下のプロパティだけはコンパイル時に実行可能:
.dup .length .keys .values
関数をコンパイル時実行するには、 コンパイル時定数が必要な箇所に関数呼び出しが現れている必要があります。 例えば:
- static変数の初期化
- static配列の次元
- テンプレートの引数
template eval( A... ) { const typeof(A[0]) eval = A[0]; } int square(int i) { return i * i; } void foo() { static j = square(3); // コンパイル時 writefln(j); writefln(square(4)); // 実行時 writefln(eval!(square(5))); // コンパイル時 }
関数のコンパイル時実行は、 ランタイム実行よりもかなり時間がかかります。 関数が無限ループするような場合、実行時ではなく、 コンパイル時にハングします。
コンパイル時実行される関数は、以下のような状況では、 実行時と異なる値を返すことがあります:
- 浮動小数点演算が、 実行時より高い精度で行われる
- 実装定義の評価順序に依存している
- 未初期化変数の使用
これらは、 最適化の設定が結果に影響を及ぼすのと同じ種類の状況です。
文字列ミックスインとコンパイル時間数実行
コンパイル時に実行される関数は、実行時にも必ず動作するものでなければいけません。 コンパイル時の関数実行は、 必ずしも実行時と同等のものであるとは限りません。 これは、関数のセマンティクスは関数のコンパイル時の値に依存してはいけない、 ということを意味しています。例を挙げると:
int foo(char[] s) { return mixin(s); } const int x = foo("1");
この例は、foo() の実行時コードが生成できないため不正です。 このようなことをしたい場合は、 関数テンプレートを使う方が適切でしょう。