関数
関数宣言
FuncDeclaration: StorageClassesopt BasicType FuncDeclarator FunctionBody AutoFuncDeclaration AutoFuncDeclaration: StorageClasses Identifier FuncDeclaratorSuffix FunctionBody FuncDeclarator: TypeSuffixesopt Identifier FuncDeclaratorSuffix FuncDeclaratorSuffix: Parameters MemberFunctionAttributesopt TemplateParameters Parameters MemberFunctionAttributesopt Constraintopt
関数パラメータ
Parameters: ( ParameterListopt ) ParameterList: Parameter Parameter , ParameterListopt VariadicArgumentsAttributesopt ... Parameter: ParameterAttributesopt BasicType Declarator ParameterAttributesopt BasicType Declarator ... ParameterAttributesopt BasicType Declarator = AssignExpression ParameterAttributesopt Type ParameterAttributesopt Type ... ParameterAttributesopt Type = AssignExpression ParameterAttributes: ParameterStorageClass UserDefinedAttribute ParameterAttributes ParameterStorageClass ParameterAttributes UserDefinedAttribute ParameterStorageClass: auto TypeCtor final in lazy out ref return scope VariadicArgumentsAttributes: VariadicArgumentsAttribute VariadicArgumentsAttribute VariadicArgumentsAttributes VariadicArgumentsAttribute: const immutable return scope shared
参照:パラメータストレージクラス。
関数属性
FunctionAttributes: FunctionAttribute FunctionAttribute FunctionAttributes FunctionAttribute: FunctionAttributeKwd Property AtAttribute MemberFunctionAttributes: MemberFunctionAttribute MemberFunctionAttribute MemberFunctionAttributes MemberFunctionAttribute: const immutable inout return scope shared FunctionAttribute
関数本体
FunctionBody: SpecifiedFunctionBody ShortenedFunctionBody MissingFunctionBody SpecifiedFunctionBody: doopt BlockStatement FunctionContractsopt InOutContractExpression doopt BlockStatement FunctionContractsopt InOutStatement do BlockStatement ShortenedFunctionBody: InOutContractExpressionsopt => AssignExpression ;
例:
int hasSpecifiedBody() { return 1; } int hasShortenedBody() => 1; // 等価である短縮関数本体の形式は、 return文を暗示する。 この構文は関数リテラルにも適用される。
短縮関数本体の形式は、 return文を暗示する。 この構文は関数リテラルにも適用される。
ボディのない関数
MissingFunctionBody: ; FunctionContractsopt InOutContractExpression ; FunctionContractsopt InOutStatement
関数(本体なし):
int foo();
abstract として宣言されていないものは、実装が 他の場所にあることが想定され、その実装はリンクステップで提供される。 これにより、関数の実装をそのユーザーから完全に隠すことができる。 また、その実装はCやアセンブラなどの別の言語で記述することも可能である。
関数の契約
FunctionContracts: FunctionContract FunctionContract FunctionContracts FunctionContract: InOutContractExpression InOutStatement InOutContractExpressions: InOutContractExpression InOutContractExpression InOutContractExpressions InOutContractExpression: InContractExpression OutContractExpression InOutStatement: InStatement OutStatement InContractExpression: in ( AssertArguments ) OutContractExpression: out ( ; AssertArguments ) out ( Identifier ; AssertArguments ) InStatement: in BlockStatement OutStatement: out BlockStatement out ( Identifier ) BlockStatement
関数の契約は、関数の前提条件と事後条件を指定する。 これらは「契約プログラミング」で使用される。
前提条件と事後条件は関数の型には影響しない。
前提条件
InContractExpressionは前提条件である。
AssertArgumentsの最初のAssignExpressionは、 必ずtrueと評価されなければならない。そうでない場合、前提条件は失敗したことになる。
2番目のAssignExpressionが存在する場合、暗黙的にconst(char)[] 型に変換できなければならない。
InStatementも前提条件である。InStatementに現れるすべてのAssertExpressionは、 InContractExpressionとなる。
前提条件は、関数の実行開始前に意味論的に満たされていなければならない。 満たされていない場合、プログラムは無効な状態に入る。
式の形式は次のとおりである。
in (expression) in (expression, "failure string") { ...function body... }
ブロック文形式は:
in { ...contract preconditions... } do { ...function body... }
事後条件
OutContractExpressionは後件である。
AssertArgumentsの最初のAssignExpressionは、 必ずtrueと評価されなければならない。そうでなければ、後件は失敗したことになる。
2番目のAssignExpressionが存在する場合、暗黙的にconst(char)[] 型に変換できなければならない。
OutStatementも後条件である。OutStatementに現れるAssertExpressionはすべて、 OutContractExpressionとなる。
関数の実行が終了した後に、意味論的に後件が満たされる必要がある。 満たされない場合、プログラムは無効な状態になる。
式の形式は次のとおりである。
out (identifier; expression) out (identifier; expression, "failure string") out (; expression) out (; expression, "failure string") { ...function body... }
ブロック文形式は:
out { ...contract postconditions... } out (identifier) { ...contract postconditions... } do { ...function body... }
どちらの型の事後条件におけるオプションの識別子も、関数の戻り値に設定され、 事後条件内からアクセスすることができる。
例
int fun(ref int a, int b) in (a > 0) in (b >= 0, "b cannot be negative!") out (r; r > 0, "return must be positive") out (; a != 0) { // 関数本体 }
int fun(ref int a, int b) in { assert(a > 0); assert(b >= 0, "b cannot be negative!"); } out (r) { assert(r > 0, "return must be positive"); assert(a != 0); } do { // 関数本体 }
この2つの関数は意味的には同一である。
In、Out、Inheritance
派生クラスの関数がスーパークラスの関数をオーバーライドする場合、 その関数の前提条件とオーバーライドされた関数の前提条件のうち、 いずれか一方のみを満たせばよい。 オーバーライドされた 関数は、前提条件を緩和するプロセスとなる。
前提条件のない関数は、その前提条件が常に満たされることを意味する。 したがって、 継承階層内の関数に前提条件がない場合、 それをオーバーライドする関数の前提条件は意味を持たない。
逆に、関数およびそのオーバーライド関数のすべての事後条件を満たさなければならない。 オーバーライド関数を追加することは、事後条件を厳格化するプロセスとなる。
関数の戻り値
関数が void ではない戻り値の型を指定している場合は、少なくとも1つのreturn 文が必要である。 ただし、
- 関数が無限ループを実行する場合
- 関数がassert(0) 文を実行する
- "関数"が型式の式を評価する noreturn
- 関数にインラインアセンブラのコードが含まれる
関数の戻り値にマークがない場合、 ref r値とみなされます。 つまり、他の関数に参照渡しできないことを意味します。
純粋関数
純粋関数は、pure 属性で注釈付けされる。 純粋関数は、グローバル変数や静的変数に直接アクセスすることはできない 。 純粋関数は、純粋関数のみを呼び出すことができる。
純粋関数は、以下のことができる。
- 関数のローカル状態を変更する。
- 例外をスローする。
int x; immutable int y; pure int foo(int i) { i++; // OK、ローカルの状態を変更する //x = i; // エラー、グローバルの状態を変更する //i = x; // エラー、変更可能なグローバルの状態を読み込む i = y; // OK、不変のグローバルの状態を読み込む throw new Exception("failed"); // OK }
純粋関数は純粋でない関数をオーバーライドできるが、 純粋でない関数によってオーバーライドされることはない。 すなわち、純粋でない関数に対しては共変である。
純粋性の強さと弱さ
弱純粋関数は、変更可能なパラメータを持つ。 プログラムの状態は、一致する引数を通じて間接的に変更される可能性がある。
pure size_t foo(int[] arr) { arr[] += 1; return arr.length; } int[] a = [1, 2, 3]; foo(a); assert(a == [2, 3, 4]);
純粋度の高い関数には、変更可能な間接参照を持つパラメータはなく 、関数外部のプログラムの状態を変更することはできない。
struct S { double x; } pure size_t foo(immutable(int)[] arr, int num, S val) { //arr[num] = 1; // コンパイルエラー num = 2; // 呼び出し側には何の副作用もない val.x = 3.14; // ditto return arr.length; }
純度の高い関数は、純度の低い関数を呼び出すことができる。
特別な場合
純粋関数は、
- 浮動小数点例外フラグの読み取りと書き込みが可能
- 浮動小数点モードフラグの読み取りと書き込みを行うことができる。ただし、 関数呼び出し時にこれらのフラグが初期状態に復元される場合に限る
デバッグ
純粋関数は、 ConditionalStatement がDebugConditionによって制御されている文において、"純粋でない"操作を実行できる。
pure int foo(int i) { debug writeln("i = ", i); // OK、デバッグ文ではpureでないコードを許可する ... }
ネストされた関数
純粋関数内部のネストされた関数は暗黙的に純粋としてマークされる。
pure int foo(int x, immutable int y) { int bar() // fooスタック・コンテキストへの隠れたコンテキスト・ポインタが // 変更可能であるため、暗黙的にピュアとマークされ、"弱くピュア"である { x = 10; // 変更可能なコンテキストポインタを通して、 // それを囲むスコープのステートにアクセスできる return x; } pragma(msg, typeof(&bar)); // int delegate() pure int baz() immutable // 隠しコンテキストポインタをimmutableに修飾し // 他のパラメータを持たないため、"強く純粋 "である { //return x; // エラー、immutableコンテキスト・ポインタを通して // 変更可能なデータにアクセスできない return y; // OK } // 純粋な入れ子関数を呼び出すことができる return bar() + baz(); }
純粋なファクトリ関数
純粋な関数とは、 変更可能な間接参照のみを含む結果を返す、純粋度の高い関数である。 呼び出しによって返されるすべての変更可能 メモリは、プログラムの他の部分から参照することはできない。 つまり、それは関数によって新たに割り当てられる。 結果の変更可能な参照も同様に、 関数呼び出し前に存在していたオブジェクトを参照することはできない。 これにより、結果は暗黙的に 何からでもimmutable またはconst shared に、 またshared およびconst shared から(非共有)const に暗黙的にキャストされます。 例えば:
struct List { int payload; List* next; } pure List* make(int a, int b) { auto result = new List(a, null); result.next = new List(b, null); return result; } void main() { auto list = make(1, 2); pragma(msg, typeof(list)); // List* immutable ilist = make(1, 2); pragma(msg, typeof(ilist)); // immutable List* pragma(msg, typeof(ilist.next)); // immutable List* }
make の結果のすべての参照は、make によって作成されたListオブジェクトを参照しており、プログラムの他の部分ではこれらのオブジェクトを参照していない。 したがって、結果は不変の変数を初期化できる。
これは関数からスローされる例外やエラーには影響しない。
最適化
int a(int) pure; // 変更可能な間接参照はできない int b(const Object) pure; // 渡された引数に依存する immutable(Object) c(immutable Object) pure; // 常にメモ可能である void g(); void f(int n, const Object co, immutable Object io) { const int x = a(n); g(); // `a`を呼び出しても`n`は変化しない int i = a(n); // `i = x`と同じである const int y = b(co); // `co`は変更可能なインダイレクトを持つことができる g(); // 別の参照を通して`co`のフィールドを変更することができる i = b(co); // 呼び出しはメモ可能ではないので、結果は異なるかもしれない const int z = b(io); i = b(io); // `i = z`と同じである }
このような関数でも、castを使用したり、パラメータのアドレスに応じて動作を変更したりするなど、 メモ化と矛盾する動作をする可能性がある。実装では、 現時点では、すべてのケースでメモ化の妥当性を強制する必要はない。
関数が例外またはエラーをスローする場合、 メモ化に関する前提はスローされた例外には適用されない。
純粋なデストラクタは、特別な省略の恩恵を受けない。
nothrow 関数
nothrow関数は、派生した例外のみをスローできる。 class Error。
Nothrow関数はスロー関数と共変である。
参照関数
ref 関数は参照渡し(値渡しではなく)で返す。ref 関数の戻り値はl値でなければならない (ref 関数の戻り値はr値でもよい)。ref 関数を呼び出して形成される式はl値である (ref 関数を呼び出して形成される式はr値である)。
int *p; ref int foo() { p = new int(2); return *p; } void main() { int i = foo(); assert(i == 2); foo() = 3; // 参照の返却値はl値になることがある assert(*p == 3); }
期限切れの関数コンテキストへの参照を返すことは許可されていない。 これには、期限切れの関数コンテキストの一部であるローカル変数、一時変数、パラメータが含まれる。
ref int sun() { int i; return i; // ローカル変数iへの参照をエスケープするとエラーになる }
ref パラメータは、ref によって返されない場合がある。ただし、 return ref。
ref int moon(ref int i) { return i; // エラー }自動関数
自動関数
自動関数の戻り値の型は、関数本体の ReturnStatementから推論される。
auto関数は戻り値の型を指定せずに宣言される。 auto関数は、auto だけでなく、有効なStorageClassであればどのようなものも使用できる。
複数のReturnStatementが存在する場合、それらの型は 暗黙的に共通の型に変換できなければならない。 ReturnStatementが存在しない場合、戻り値の型は void 型と推論される。
auto foo(int x) { return x + 3; } // intと推測される pure bar(int x) { return x; return 2.5; } // doubleと推測される
自動参照関数
自動参照関数は、"auto関数"と同様に返り値の型を推論できる。 さらに、以下のすべてに該当する場合は参照関数となる
- 関数から返される式がすべて"l値"である
- ローカル変数が返されない
- 返されるパラメータが参照パラメータである
- 各返却式は、推測された返却型における"l値"に暗黙的に変換されなければならない
auto ref f1(int x) { return x; } // 値を返す auto ref f2() { return 3; } // 値を返す auto ref f3(ref int x) { return x; } // refを返す auto ref f4(out int x) { return x; } // refを返す auto ref f5() { static int x; return x; // refを返す }
関数の参照性は、関数本体のすべての ReturnStatementから決定される。
auto ref f1(ref int x) { return 3; return x; } // OK、値を返す auto ref f2(ref int x) { return x; return 3; } // OK、値を返す auto ref f3(ref int x, ref double y) { return x; return y; // 返却値はdoubleと推論されるが、cast(double)xはl値ではない、 // したがって、f3は戻り値を持つ。 }
自動参照関数には明示的な戻り値の型を指定できる。
auto ref int bar(ref int x) { return x; } // OK、refを返す auto ref int foo(double x) { return x; } // エラー、doubleをintに変換できない
入出力関数
詳細については、inout 型修飾子を参照のこと。
オプションの括弧
関数呼び出しに明示的な引数が渡されない場合、すなわち、構文上は() を使用することになる場合、これらの括弧は、 プロパティ関数のゲッター呼び出しと同様に、省略することができる。 UFCS呼び出しでも、空の括弧を省略することができる。
void foo() {} // 引数がない void fun(int x = 10) {} void bar(int[] arr) {} void main() { foo(); // OK foo; // これもOK fun; // OK int[] arr; arr.bar(); // UFCS呼び出し arr.bar; // これもOK }
曖昧さがあるため、デリゲートまたは関数ポインタを呼び出すには括弧が必要である。
void main() { int function() fp; assert(fp == 6); // エラー、互換性のない型int function()とint assert(*fp == 6); // エラー、互換性のない型int()およびint int delegate() dg; assert(dg == 6); // エラー、互換性のない型int delegate()およびint }
関数がデリゲートまたは関数ポインタを返す場合、括弧は 結果ではなく関数呼び出しに適用される。結果を直接呼び出すには、括弧を2セット使用する必要がある。
int getNum() { return 6; } int function() getFunc() { return &getNum; } void main() { int function() fp; fp = getFunc; // 暗黙の呼び出し assert(fp() == 6); fp = getFunc(); // 明示的呼び出し assert(fp() == 6); int x = getFunc()(); assert(x == 6); }
struct S { int getNum() { return 6; } int delegate() getDel() return { return &getNum; } } void main() { S s; int delegate() dg; dg = s.getDel; // 暗黙の呼び出し assert(dg() == 6); dg = s.getDel(); // 明示的呼び出し assert(dg() == 6); int y = s.getDel()(); assert(y == 6); }
プロパティ関数
警告:プロパティ関数の定義と有用性は現在見直されており、実装は 現在不完全である。プロパティ関数の使用は、定義が より明確になり、実装がより成熟するまで推奨されない。
プロパティとは、構文上はフィールドまたは変数として扱うことができる関数である。 プロパティは読み取りおよび書き込みが可能である。 プロパティは、引数なしでメソッドまたは関数を呼び出すことで読み取られる。 プロパティは、設定されている値を引数としてメソッドまたは関数を呼び出すことで書き込まれる。
シンプルなゲッターおよびセッタープロパティは、UFCSを使用して記述できる。 これらのプロパティは、関数に@property 属性を追加することで拡張でき、これにより、 以下の動作が追加される。
- @property 同じ名前の 以外の関数とオーバーロードすることはできない。@property
- @property 関数は、0個、1個、または2個のパラメータのみを持つことができる。
- @property 関数には可変長引数を使用できない。
- 式typeof(exp) において、exp が@property 関数である場合、 その型は関数の型ではなく、関数の戻り値の型となる。
- __traits(compiles, exp) という式では、exp が@property 関数であるため、 関数を呼び出せるかどうかについて、さらにチェックが行われる。
- @property それぞれ異なる方法で文字列が変更されるため、 は 異なるコンパイル単位でも一貫して使用する必要がある。@property
- ObjectiveCインターフェースは、@property セッター関数を特別なものとして認識し、 それに応じて変更する。
単純なプロパティは次のようになる。
xml-ph-0000@deepl.internalstruct Foo { @property int data() { return m_data; } // プロパティの読み込み @property int data(int value) { return m_data = value; } // プロパティの書き込み private: int m_data; }
使用方法:
int test() { Foo f; f.data = 3; // f.data(3)と同じ; return f.data + 3; // return f.data() + 3と同じ; }
読み取りメソッドが存在しない場合は、そのプロパティは書き込み専用であることを意味する。 書き込みメソッドが存在しない場合は、そのプロパティは読み取り専用であることを意味する。 複数の書き込みメソッドが存在してもよい。正しいメソッドは、 通常の関数オーバーロードのルールに従って選択される。
その他の点では、これらのメソッドは他のメソッドと同様である。 静的メソッドであったり、異なるリンクを持ったり、アドレスを取得したりすることができる。
組み込みプロパティ.sizeof 、.alignof 、.mangleofは、 構造体、共用体、クラス、または列挙型でフィールドまたはメソッドとして宣言することはできない。
プロパティ関数にパラメータが指定されていない場合、ゲッターとして機能する。 パラメータが1つだけ指定されている場合、セッターとして機能する。
仮想関数
仮想関数は、クラスメンバ関数であり、vtbl[] と呼ばれる関数ポインタテーブルを介して間接的に呼び出される。 仮想関数は、派生クラスでオーバーライドすることができる。
class A { void foo(int x) {} } class B : A { override void foo(int x) {} //override void foo() {} // エラー、Aにfoo()がない } void test() { A a = new B(); a.foo(1); // B.foo(int)を呼び出す }
関数をオーバーライドする場合は、override 属性が必要である。 これは、ベースクラスのメンバ関数のパラメータが変更され、 すべての派生クラスでオーバーライドする関数を更新する必要がある場合に エラーを検出するのに役立つ。
finalメソッド属性は、 サブクラスによるメソッドのオーバーライドを禁止する。
以下は仮想ではない。
- 構造体および共用体のメンバ関数
- final メンバ関数
- staticメンバー関数
- private であるメンバ関数、またはpackage
- メンバテンプレート関数
Example:
class A { int def() { ... } final int foo() { ... } final private int bar() { ... } private int abc() { ... } } class B : A { override int def() { ... } // OK、A.defをオーバーライドする override int foo() { ... } // エラー、A.fooはfinal int bar() { ... } // OK、A.abcはfinal private、しかしvirtualでない int abc() { ... } // OK、A.abcはvirtualではないが、B.abcはvirtualである } void test() { A a = new B; a.def(); // B.defを呼び出す a.foo(); // A.fooを呼び出す a.bar(); // A.barを呼び出す a.abc(); // A.abcを呼び出す }
Objective-C リンクを持つメンバ関数は、final またはstatic が指定されていても仮想関数であり、 オーバーライドが可能である。
共分散
オーバーライド関数は、オーバーライドされた関数と共変である場合がある。 共変関数は、オーバーライドされた関数の型に暗黙的に変換できる型を持つ。
class A { } class B : A { } class Foo { A test() { return null; } } class Bar : Foo { // Foo.test()をオーバーライドし、Foo.test()と共変する override B test() { return null; } }
ベースクラスのメソッドを呼び出す
ベースクラスのメンバ関数を直接呼び出すには、Base 、 関数名の前にBase. を記述する。 これにより、関数ポインタを介した動的ディスパッチが回避される。 例:
class B { int foo() { return 1; } } class C : B { override int foo() { return 2; } void test() { assert(B.foo() == 1); // this.B.foo()に変換され、 // B.fooを静的に呼び出す。 assert(C.foo() == 2); // 'this'の実際のインスタンスがDであっても、 // C.fooを静的に呼び出す。 } } class D : C { override int foo() { return 3; } } void main() { auto d = new D(); assert(d.foo() == 3); // D.fooを呼び出す assert(d.B.foo() == 1); // B.fooを呼び出す assert(d.C.foo() == 2); // C.fooを呼び出す d.test(); }
ベースクラスのメソッドも参照を通じて呼び出すことができる super参照を通じて呼び出すこともできます。
オーバーロードセットとオーバーライド
オーバーロード解決を行う際には、ベースクラスの関数は考慮されない。 これは、それらが同じ オーバーロードセットに属さないためである。
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); // A.foo(int)は考慮されないので、B.foo(long)を呼び出す A a = b; a.foo(1); // 実行時エラーが発生する(A.foo(int)を呼び出す代わりに) }
オーバーロード解決プロセスに基本クラスの関数を含めるには、 エイリアス宣言を使用する。
class A { int foo(int x) { ... } int foo(long y) { ... } } class B : A { alias foo = A.foo; override int foo(long x) { ... } } void test() { A a = new B(); a.foo(1); // A.foo(int)を呼び出す B b = new B(); b.foo(1); // A.foo(int)を呼び出す }
このようなエイリアス宣言が使用されていない場合、派生 クラスの関数は、ベースクラスの同名の関数を完全に上書きする 。たとえベースクラスの関数のパラメータの型が異なっていても 同様である。 ベースクラスへの暗黙の変換によって、それらの他の関数が 呼び出される場合は、それは不正である。
class A { void set(long i) { } void set(int i) { } } class B : A { override void set(long i) { } } void test() { A a = new B; a.set(3); // エラー、A.set(int)の使用はBによって隠される // 'alias set = A.set;'を使用して、ベースクラスのオーバーロードsetを導入する }
デフォルト値
関数パラメータのデフォルト値は継承されない。
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に引数が必要である }
継承された属性
オーバーライド関数は、オーバーライドされた関数の属性から、指定されていないすべてのFunctionAttributes を継承する。
class B { void foo() pure nothrow @safe {} } class D : B { override void foo() {} } void main() { auto d = new D(); pragma(msg, typeof(&d.foo)); // コンパイル時に"void delegate() pure nothrow @safe"と表示される }
制限事項
属性 @disableおよび deprecated オーバーライド関数では許可されていない。
class B { void foo() {} } class D : B { @disable override void foo() {} // エラー、オーバーライド関数に@disableを適用できない }xml-ph-0000@deepl.internalxml-ph-0000@deepl.internal xml-ph-0000@deepl.internalxml-ph-0000@deepl.internal
インライン関数
コンパイラが関数をインライン化するかどうかを決定する。 この決定は、 pragma(inline)。
関数のオーバーロード
関数のオーバーロードは、同じスコープ内の2つ以上の関数に 同じ名前が指定された場合に発生する。 選択される関数は、引数に最も適合するものである。 適合レベルは以下の通りである。
- 一致しない
- 暗黙的変換による一致
- 修飾子変換による一致(引数の型が パラメータ型に修飾子変換可能である場合)
- 完全一致
名前付き引数は、 「引数とパラメータの一致」に従って候補が解決される。 これが失敗した場合(例えば、オーバーロードに名前付き引数と一致するパラメータがない場合など)、 一致レベルは一致しない。それ以外の場合、名前付き引数は一致レベルに影響しない。
各引数(this 参照を含む)は、 その引数の一致レベルを決定するために、関数の対応するパラメータと比較される。 関数の一致レベルは、 その関数の各引数のうち最も一致レベルが低いものとなる。
リテラルは、ref またはout パラメータに一致しない。
scope パラメータのストレージクラスは関数のオーバーロードには影響しない。
2つ以上の関数が同じマッチレベルを持つ場合、 部分順序 を使用して、最適なマッチを見つけるためにあいまい性を解消する。 部分順序は、最も特化した関数を見つける。 どちらの関数も他方よりも特化されていない場合、 それはあいまい性のエラーである。 部分順序は、関数 f およびgについて、fのパラメータ型を取り、 それらの型のデフォルト値を取り、引数のリストを構築し、 それらをgと照合しようとする。 成功すれば、gは少なくともfと同じくらい特化されている 。 例:
class A { } class B : A { } class C : B { } void foo(A); void foo(B); void test() { C c; /* foo(A)もfoo(B)も暗黙の変換(レベル2)で一致する。 * 部分順序規則を適用すると、 * foo(B)はAで呼び出すことはできず、foo(A)はBで * 呼び出すことができる。したがって、foo(B)の方がより特殊であり、選択される。 */ foo(c); // foo(B)を呼び出す }
可変長引数を持つ関数は、 可変長引数を持たない関数よりも特化されていないとみなされる。
staticメンバ関数は、メンバ関数でオーバーロードすることができる。 staticメンバ関数の構造体、クラス、 または共用体は、this 引数の型から推測される。
struct S { void eggs(int); static void eggs(long); } S s; s.eggs(0); // void eggs(int);を呼び出す S.eggs(0); // エラー: `this`が必要 s.eggs(0L); // static void eggs(long);を呼び出す S.eggs(0L); // static void eggs(long);を呼び出す struct T { void bacon(int); static void bacon(int); } T t; t.bacon(0); // エラー: あいまい T.bacon(0); // エラー: あいまい
オーバーロード セット
同じスコープで宣言された関数は、互いにオーバーロードされ、 オーバーロードセットと呼ばれる。 オーバーロードセットの例としては、モジュールレベルで定義された関数がある。
module A; void foo() { } void foo(long i) { }
A.foo() A.foo(long) はオーバーロードセットを形成する。 異なるモジュールでも、同じ名前の関数の別のオーバーロードセットを定義できる。
module B; class C { } void foo(C) { } void foo(int i) { }
また、AとBはサードパーティのモジュールCによってインポートされる。A.foo のオーバーロードセットとB.fooのオーバーロードセットの両方が、シンボルfoo を検索すると見つかる。foo のインスタンスは、
import A; import B; void bar(C c , long i) { foo(); // A.foo()を呼び出す foo(i); // A.foo(long)を呼び出す foo(c); // B.foo(C)を呼び出す foo(1,2); // エラー、どのfooにもマッチしない foo(1); // エラー、A.foo(long)とB.foo(int)にマッチする A.foo(1); // A.foo(long)を呼び出す }
B.foo(int) は A.foo(long) よりもfoo(1) により適合しているが、 2つの適合が異なるオーバーロードセットにあるため、エラーとなる。
オーバーロードセットはエイリアス宣言でマージできる。
import A; import B; alias foo = A.foo; alias foo = B.foo; void bar(C c) { foo(); // A.foo()を呼び出す foo(1L); // A.foo(long)を呼び出す foo(c); // B.foo(C)を呼び出す foo(1,2); // エラー、どのfooにもマッチしない foo(1); // B.foo(int)を呼び出す A.foo(1); // A.foo(long)を呼び出す }xml-ph-0000@deepl.internalxml-ph-0000@deepl.internal
関数パラメータ
パラメータ ストレージクラス
パラメータの保存クラスは、in 、out 、ref 、lazy 、return 、scope である。 また、パラメータは、const 、immutable 、shared 、inout という型コンストラクタを取ることもできる。
in、out 、ref 、lazy は相互に排他的である。最初の3つは、 それぞれ入力、出力、入出力パラメータを意味する。 例:
int read(in char[] input, ref size_t count, out int errno); void main() { size_t a = 42; int b; int r = read("Hello World", a, b); }
read 3つのパラメータがある。 は読み取りのみで、参照は保持されない。 は読み取りと書き込みが可能で、 は関数内部から"値"が設定される。input count errno
引数"Hello World" はパラメータinput にバインドされ、a はcount に、b はerrno にバインドされる。
Storage Class | Description |
---|---|
なし | パラメータは、引数の"変更可能なコピー"となる。 |
in | パラメータは関数への入力となる。 |
out | 引数は「l値」でなければならず、参照渡しされ、 関数呼び出し時にその型のデフォルト値(T.init )で初期化される。 |
ref | パラメータは入出力パラメータであり、参照渡しされる。 |
scope | このパラメータは関数呼び出しから逃れてはならない (例えばグローバル変数に割り当てられるなど)。 参照型ではないパラメータは無視される。 |
return | パラメータは、最初のパラメータに返却またはコピーされる可能性があるが、 それ以外の場合には関数から抜け出さない。 このようなコピーは、派生元の引数を上書きしないようにする必要がある。 参照のないパラメータは無視される。 スコープパラメータを参照のこと。 |
lazy | 引数は呼び出された関数によって評価され、呼び出し元によって評価されるのではない |
Type Constructor | Description |
const | 引数は暗黙的に const 型に変換される |
immutable | 引数は暗黙的に不変型に変換される |
shared | 引数は共有型に暗黙的に変換される |
inout | 引数はinout型に暗黙的に変換される |
パラメータ内
注釈: 以下の例では、-preview=in スイッチが必要であり、 v2.094.0 以降で使用可能である。 使用しない場合、in はconst と同等である。パラメータは関数への入力である。入力パラメータは、const scope ストレージクラスを持つかのように動作する。 入力パラメータは、コンパイラによって参照渡しされる場合もある。
xml-ph-0000@deepl.internalパラメータとは異なり、xml-ph-0001@deepl.internalパラメータは左辺値と右辺値の両方にバインドできる (リテラルなど)。ref パラメータとは異なり、in パラメータは、"l値"と"r値"の両方にバインドできる (リテラルなど)。
値として渡された場合に副作用が発生する型(コピーコンストラクタ、postblit、デストラクタを持つ型など)や、 コピーできない型 (例えば、コピーコンストラクタが@disable としてマークされている場合など)は、常に参照渡しされる。 動的配列、クラス、連想配列、関数ポインタ、およびデリゲートは 常に値渡しされる。
参照および出力パラメータ
デフォルトでは、パラメータは"r値"引数を取る。ref パラメータは"l値"引数を取るため、その値の変更は 呼び出し元の引数に対して行われる。
void inc(ref int x) { x += 1; } void seattle() { int z = 3; inc(z); assert(z == 4); }
ref パラメータは参照渡しすることも可能である。 「参照パラメータの返却」を参照。
out パラメータは、ref パラメータと似ているが、 関数が呼び出された際にx.init で初期化される点が異なる。
void zero(out int x) { assert(x == 0); } void two(out int x) { x = 2; } void tacoma() { int a = 3; zero(a); assert(a == 0); int y = 3; two(y); assert(y == 2); }
動的配列およびクラスオブジェクトパラメータは、常に参照渡しされる。out およびrefは 参照のみに適用され、内容には適用されない。
Lazy Parameters
lazy パラメータへの引数は、関数が呼び出される前に評価されることはない。 引数は、関数内でパラメータが評価された場合にのみ評価される。したがって、lazy 引数は0回またはそれ以上実行される可能性がある。
import std.stdio : writeln; void main() { int x; 3.times(writeln(x++)); writeln("-"); writeln(x); } void times(int n, lazy void exp) { while (n--) exp(); }
コンソールに表示される:
0 1 2 − 3
lazy パラメータはl値であることはできない。
lazy パラメータの基底デリゲートは、& 演算子を使用することで抽出できる可能性がある。
void test(lazy int dg) { int delegate() dg_ = &dg; assert(dg_() == 7); assert(dg == dg_()); } void main() { int a = 7; test(a); }
lazy パラメータの型void は、 あらゆる型の引数を受け入れることができる。
参照:遅延可変長引数関数
デフォルト引数
関数パラメータ宣言にはデフォルト値を指定できる。
void foo(int x, int y = 3) { ... } ... foo(4); // foo(4, 3)と同じ;
デフォルトのパラメータは、関数宣言のコンテキストで解決され、意味的にチェックされる。
module m; private immutable int b; pure void g(int a = b) {}xml-ph-0000@deepl.internal
import m; int b; pure void f() { g(); // OK、m.bを使う }
デフォルト式が使用される場所では、AssignExpressionの属性が適用される。
module m; int b; pure void g(int a = b) {}xml-ph-0000@deepl.internal
import m; enum int b = 3; pure void f() { g(); // エラー、pure関数で変更可能なグローバルの`m.b`にアクセスできない }
参照:デフォルト値付き関数型エイリアス 。
functionfunctionリターン参照パラメータ
参照を返すパラメータは、 参照関数とともに使用され、 返された参照が対応する引数の有効期間を越えて存続しないことを保証する。
ref int identity(return ref int x) { return x; // 何もしないパススルー関数だ } ref int fun() { int x; return identity(x); // エラー: ローカル変数xへの参照をエスケープする } ref int gun(return ref int x) { return identity(x); // OK }
ref 変数のアドレスを返すことも、@safe コードでチェックされる。
int* pluto(ref int i) @safe { return &i; // エラー: &iを返却すると、パラメータiへの参照がエスケープされる } int* mars(return ref int i) @safe { return &i; // -preview=dip1000でOK }
関数がvoid を返し、最初のパラメータがref またはout の場合、 それ以降のすべてのreturn ref パラメータは、 寿命チェックのために最初のパラメータに割り当てられているとみなされる。 構造体の非staticメンバ関数へのthis 参照パラメータは、 最初のパラメータとみなされる。
struct S { private int* p; void f(return ref int i) scope @safe { p = &i; } } void main() @safe { int i; S s; s.f(i); // -preview=dip1000でOK、`s`のライフタイムは`i`よりも短い *s.p = 2; assert(i == 2); }
return ref パラメータが複数ある場合、戻り値の寿命は、 対応する引数のうち最も短い寿命となる。
return ref パラメータの型、および戻り値の型は、 戻り値の有効期間を決定する際に考慮されない。
戻り値の型に間接参照が含まれていなくてもエラーにはならない。
int mercury(return ref int i) { return i; // OK }
テンプレート関数、auto関数、ネストされた関数、ラムダ関数は return 属性を推論できる。
@safe: ref int templateFunction()(ref int i) { return i; // OK } ref auto autoFunction(ref int i) { return i; // OK } void uranus() { ref int nestedFunction(ref int i) { return i; // OK } auto lambdaFunction = (ref int i) { return &i; // OK }; }
構造体 戻り値 メソッド
構造体の非静的メソッドは、return 属性を指定することで、 構造体インスタンスが破棄された際に参照が残存しないように することができる。
struct S { private int x; ref int get() return { return x; } } ref int escape() { S s; return s.get(); // エラー: ローカル変数sへの参照をエスケープする }
隠しパラメータthis は、return ref となる。
return 属性は、 メソッドがref でない場合でも、返された値の有効期間を制限するために使用できる。
struct S { private int i; int* get() return @safe => &i; } void f() @safe { int* p; { S s; int *q = s.get(); // OK、qはsより寿命が短い p = s.get(); // エラー、pの方が有効期間が長い p = (new S).get(); // OK、ヒープにSが割り当てられている } }
スコープパラメータ
参照型のscope パラメータは、関数呼び出しからエスケープしてはならない (例えば、グローバル変数に割り当てられるなど)。参照型以外のパラメータには影響しない。scope エスケープ解析は、@safe 関数に対してのみ行われる。他の関数については、scopeセマンティクスを手動で強制する必要がある。
@safe: int* gp; void thorin(scope int*); void gloin(int*); int* balin(scope int* q, int* r) { gp = q; // エラー、qはグローバルのgpにエスケープする gp = r; // OK thorin(q); // OK、qはthorin()をエスケープしない thorin(r); // OK gloin(q); // エラー、gloin()はqをエスケープする gloin(r); // OK、gloin()はrをエスケープする return q; // エラー、'scope'を返却できないq return r; // OK }
scope パラメータはエスケープしてはならないため、コンパイラは潜在的にscope パラメータへの一意の引数のヒープ割り当てを回避できる。このため、配列リテラル、デリゲート リテラル、またはNewExpression をスコープパラメータに渡すことが、 コンパイラの実装によっては@nogc コンテキストで許可される可能性がある。
スコープパラメータを返す
return scope とマークされたパラメータは、間接参照を含む 場合、その間接参照から逃れるには関数の戻り値を使用するしかない。
@safe: int* gp; void thorin(scope int*); void gloin(int*); int* balin(return scope int* p) { gp = p; // エラー、p escapes to global gp thorin(p); // OK、p does not escape thorin() gloin(p); // エラー、gloin()はpをエスケープする return p; // OK }
クラス参照はポインタとみなされ、scope の対象となる。
@safe: class C { } C gp; void thorin(scope C); void gloin(C); C balin(return scope C p, scope C q, C r) { gp = p; // エラー、pはグローバルのgpにエスケープする gp = q; // エラー、qはグローバルのgpにエスケープする gp = r; // OK thorin(p); // OK、pはthorin()をエスケープしない thorin(q); // OK thorin(r); // OK gloin(p); // エラー、gloin()はpをエスケープする gloin(q); // エラー、gloin()はqをエスケープする gloin(r); // OK、gloin()はrをエスケープする return p; // OK return q; // エラー、'scope'を返却できないq return r; // OK }
return scope クラスおよびインターフェースのメンバ関数の に適用できる。this
class C { C bofur() return scope { return this; } }
テンプレート化された関数、"auto"関数、ネストされた関数、および ラムダ関数は、return scope 属性を推論できる。
参照 戻り値 スコープ パラメータ
同じパラメータに対して、return ref とreturn scope の両方のセマンティクスを指定することはできない。 パラメータがref で渡され、return とscope の両方のストレージクラスを持つ場合、 return scopereturn とscopeのキーワードが この順序で隣り合って表示されている場合にのみ、意味を持つ。return ref とscope パラメータを指定すると、スコープポインタへの参照を返すことができる。 それ以外の場合は、パラメータは return ref意味論 および通常の scope。
U xerxes( ref return scope V v) // (1) refとreturn scope U sargon(return ref scope V v) // (2) return refとscope struct S { // 注釈: 構造体メンバ関数では、暗黙の`this`パラメータは // `ref`で渡される U xerxes() return scope; // return scope U sargon() scope return; // return ref、`return`は`scope`の後に来る U xerxes() return const scope; // return ref、`return`と`scope`は隣接しない }
return scope 、return ref 、scope のセマンティクスの組み合わせの例:
xml-ph-0000@deepl.internal@safe: int* globalPtr; struct S { int val; int* ptr; this(return scope ref int* p) { ptr = p; } // 構造体では`this`は`ref`から渡される int* retRefA() scope return // return-ref, scope { globalPtr = this.ptr; // 不許可、`this`は`scope`である return &this.val; // 許可、`return`は`return ref`を意味する } ref int retRefB() scope return // return-ref, scope { globalPtr = this.ptr; // 不許可、`this`は`scope`である return this.val; // 許可、`return`は`return ref`を意味する } int* retScopeA() return scope // ref, return-scope { return &this.val; // 不許可、`this`への参照をエスケープする return this.ptr; // 許可、`return scope`ポインタを返す } ref int retScopeB() return scope // ref, return-scope { return this.val; // 不許可、`this`への参照をエスケープする return *this.ptr; // 許可、`return scope`ポインタを返す } ref int* retRefScopeC() scope return // return-ref, scope { return this.ptr; // 許可、スコープポインタへの参照を返す } } int* retRefA(return ref scope S s) { globalPtr = s.ptr; // 不許可、`s`は`scope`である return &s.val; // 許可、`return ref s`への参照を返す } ref int retRefB(return ref scope S s) { globalPtr = s.ptr; // 不許可、`s`は`scope`である return s.val; } int* retScopeA(ref return scope S s) { return &s.val; // 不許可、`s`への参照をエスケープする return s.ptr; // 許可、`return scope`ポインタを返す } ref int retScopeB(ref return scope S s) { return s.val; // 不許可、`s`への参照をエスケープする return *s.ptr; // 許可、`return scope`ポインタを返す } ref int* retRefScopeC(return ref scope int* p) { return p; // 許可、スコープポインタへの参照を返す }
pure 関数におけるscope パラメータの推論
パラメータにマークまたは推論scope が指定されていない場合でも、関数呼び出しでscope ポインタを割り当てるために@safe 指定できる可能性がある。 以下の条件を満たす必要がある。- 関数である pure、したがって引数はグローバル変数に割り当てることができない
- 関数は nothrow、したがって、引数はスローされたException オブジェクトに割り当てることができない
- 他のパラメータには変更可能な間接参照がないため、引数はより長寿命の変数に割り当てることができない
- 関数がref で返されるか、ポインタを含む返り値の型を持つ場合、引数は返される可能性があるため、 として扱われる。return scope
- そうでなければ、その引数は関数から抜け出すことができないため、xml-ph-0000@deepl.internalとして扱われる。scope
@safe: int dereference(int* x) pure nothrow; int* identity(int* x) pure nothrow; int* identityThrow(int* x) pure; void assignToRef(int* x, ref int* escapeHatch) pure nothrow; void assignToPtr(int* x, int** escapeHatch) pure nothrow; void cannotAssignTo(int* x, const ref int* noEscapeHatch) pure nothrow; int* globalPtr; int* test(scope int* ptr) { int result = dereference(ptr); // 許可、`scope`として扱われる int* ptr2 = identity(ptr); // 許可、`return scope`として扱われる int* ptr3 = identityThrow(ptr); // 不可、`Exception`をスローすることができる assignToRef(ptr, globalPtr); // 不可、2番目のパラメータを変更可能 assignToPtr(ptr, &globalPtr); // 不可、2番目のパラメータを変更可能 cannotAssignTo(ptr, globalPtr); // 許可 return ptr2; // 不可、ptr2 が`scope`と推測される }
パラメータのユーザー定義属性
参照:ユーザー定義属性可変長引数関数
可変長引数関数は、可変個の引数を取る。 3つの形式がある:
C言語のスタイルによる可変長引数関数
C言語のスタイルの可変長引数関数は、 最後の関数パラメータとしてパラメータ... を指定して宣言される。extern (C) などのように、"D言語のスタイル"ではないリンクが使用される。
可変長引数にアクセスするには、 標準ライブラリモジュールをインポートする core.stdc.stdarg。
import core.stdc.stdarg; extern (C) void dry(int x, int y, ...); // C言語のスタイルの可変長引数関数である void spin() { dry(3, 4); // OK、可変長引数はない dry(3, 4, 6.8); // OK、可変長引数が1つ dry(2); // エラー、パラメータyの引数がない }
少なくとも1つの可変長でないパラメータが宣言されていなければならない。
extern (C) int def(...); // エラー、少なくとも1つのパラメータが必要
C言語のスタイルの可変長引数関数は、C言語の可変長引数関数の呼び出し規約に一致しており、printf のようなC標準ライブラリ関数を呼び出すことができる。
extern (C) int printf(const(char)*, ...); void main() { printf("hello world\n"); }
C言語のスタイルの可変長引数関数は、@safe としてマークすることはできない。
C-styleC-stylevoid wash() { rinse(3, 4, 5); // 最初の可変長引数は5である } import core.stdc.stdarg; extern (C) void rinse(int x, int y, ...) { va_list args; va_start(args, y); // yは最後の名前付きパラメータである int z; va_arg(args, z); // zは5に設定されている va_end(args); }variadicvariadic
D言語のスタイルの可変長引数関数
D言語のスタイルの可変長引数関数はDリンケージであり、... を最後のパラメータとする。
... 唯一のパラメータとすることもできます。
... パラメータの前にパラメータがある場合は、... とそれらを区切るカンマが必要である。
int abc(char c, ...); // 必要なパラメータは1つ: c int def(...); // 必須パラメータはない int ghi(int i ...); // 型安全な可変長引数関数である //int boo(, ...); // エラー
2つの隠し引数が関数に渡される。
- void* _argptr
- TypeInfo[] _arguments
_argptr 可変長引数の最初の引数への参照である。 可変長引数にアクセスするには、 import core.vararg。_argptr をcore.va_arg と組み合わせて使用する:
import core.vararg; void test() { foo(3, 4, 5); // 最初の可変長引数は5である } @system void foo(int x, int y, ...) { int z = va_arg!int(_argptr); // zが5に設定され、_argptrが進められる // 次の引数に進む }
_arguments 引数の数とそれぞれの を 指定し、実行時に型安全性をチェックできるようにする。typeid
import std.stdio; void main() { Foo f = new Foo(); Bar b = new Bar(); writefln("%s", f); printargs(1, 2, 3L, 4.5, f, b); } class Foo { int x = 3; } class Bar { long y = 4; } import core.vararg; @system void printargs(int x, ...) { writefln("%d arguments", _arguments.length); for (int i = 0; i < _arguments.length; i++) { writeln(_arguments[i]); 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%s", f); } else if (_arguments[i] == typeid(Bar)) { Bar b = va_arg!(Bar)(_argptr); writefln("\t%s", b); } else assert(0); } }
0x00870FE0 5 arguments int 2 long 3 double 4.5 Foo 0x00870FE0 Bar 0x00870FD0
D言語のスタイルの可変長引数関数は、@safe としてマークすることはできない。
Typesafe 可変長引数関数
型安全な可変長引数関数は D リンクであり、可変長引数 パラメータは配列またはクラスとして宣言される。 配列またはクラスは引数から構築され、 配列またはクラスオブジェクトとして渡される。
動的配列の場合:
int sum(int[] ar ...) // 型安全な可変長引数関数 { int s; foreach (int x; ar) s += x; return s; } import std.stdio; void main() { writeln(stan()); // 6 writeln(ollie()); // 15 } int stan() { return sum(1, 2, 3) + sum(); // 6+0を返却する } int ollie() { int[3] ii = [4, 5, 6]; return sum(ii); // 15を返却する }
静的配列の場合、引数の数は 配列の次元と一致しなければならない。
int sum(int[3] ar ...) // 型安全な可変長引数関数 { int s; foreach (int x; ar) s += x; return s; } int frank() { return sum(2, 3); // エラー、配列に3つの値が必要 return sum(1, 2, 3); // 6を返却する } int dave() { int[3] ii = [4, 5, 6]; int[] jj = ii; return sum(ii); // 15を返却する return sum(jj); // エラー、型の不一致 }
クラスオブジェクトの場合:
int tesla(int x, C c ...) { return x + c.x; } class C { int x; string s; this(int x, string s) { this.x = x; this.s = s; } } void edison() { C g = new C(3, "abc"); tesla(1, c); // OK、cはCのインスタンスなので tesla(1, 4, "def"); // OK tesla(1, 5); // エラー、Cに一致するコンストラクタがない }
可変長引数クラスオブジェクトまたは配列インスタンスの寿命は、 関数の終了時に終了する。
C orville(C c ...) { return c; // エラー、Cのインスタンスの返却値が不正 } int[] wilbur(int[] a ...) { return a; // エラー、返値後の配列の内容が無効 return a[0..1]; // エラー、返値後の配列の内容が無効 return a.dup; // OK、コピーが作成されたので }
他の型の場合は、引数は値として渡される。
int neil(int i ...) { return i; } void buzz() { neil(3); // 3を返却する neil(3, 4); // エラー、引数が多すぎる int[] x; neil(x); // エラー、型の不一致 }
遅延可変長引数関数
関数の可変長引数が、パラメータを持たないデリゲートの配列である場合、 その引数の型がデリゲートの型と一致しない場合は、 その引数の型がデリゲートの型と一致しない場合は、その引数の型がデリゲートの型と一致しない場合は、その引数の型がデリゲートの型と一致しない場合は、その引数の型がデリゲートの型と一致しない場合は、その引数の型がデリゲートの型と一致しない場合は、
void hal(scope int delegate()[] dgs ...); void dave() { int delegate() dg; hal(1, 3+x, dg, cast(int delegate())null); // (1) hal( { return 1; }, { return 3+x; }, dg, null ); // (1)と同じ }
可変長引数デリゲート配列は、遅延評価の可変長引数配列とは異なる。 前者の場合、各配列要素へのアクセスは すべての配列要素を評価する。 後者の場合、アクセスされる要素のみが評価される。
import std.stdio; void main() { int x; ming(++x, ++x); int y; flash(++y, ++y); } // 遅延可変長配列 void ming(lazy int[] arr...) { writeln(arr[0]); // 1 writeln(arr[1]); // 4 } // 可変長デリゲート配列 void flash(scope int delegate()[] arr ...) { writeln(arr[0]()); // 1 writeln(arr[1]()); // 2 }
- 非staticメンバ関数はすべて、隠しパラメータを持つ。 this 参照は、 関数が呼び出されたオブジェクトを参照する。
- D言語のスタイルの可変長引数関数には 隠しパラメータがある。
- Objective-C リンク関数には、追加の隠しパラメータがあり、 名前は付けられていないが、呼び出されたセレクタである。
参照 スコープ リターン ケース
caseケース定義
Term | Description |
---|---|
I | 指示語を含まない |
P | 間接を含む型 |
X | 間接参照を含む場合と含まない場合がある型 |
p | P型のパラメータ |
i | パラメータの型I |
参照 | ref または パラメータout |
戻り値 | return 文によって返された |
エスケープされ | グローバル変数またはその他のメモリに格納され、関数のスタックフレームには含まれない |
分類
パラメータは以下の状態のいずれかである必要がある。
Term | Description |
---|---|
なし | p エスケープされるか、エスケープされる可能性がある |
ReturnScope | p 返される可能性はあるが、エスケープはされない |
Scope | p 返却もエスケープもできない |
Ref | p 返却またはエスケープされる場合がある。ref は返却もエスケープもされない場合がある。 |
ReturnRef | p 返却またはエスケープされる場合がある。ref は返却されるがエスケープされない場合がある |
RefScope | p 返却もエスケープもできない。ref は返却もエスケープもできない。 |
ReturnRef-Scope | p 返却もエスケープもできない。ref は返却できるが、エスケープはできない。 |
Ref-ReturnScope | p 返却はできるがエスケープはできない。ref は返却もエスケープもできない。 |
ReturnRef-ReturnScope | p エスケープせずに返すことができる。ref はエスケープせずに返すことができる。 これは現在の構文では表現できないため 許可されていない。 |
分類へのマッピング構文
scope の直前にreturn が並置されている場合は、ReturnScope を意味する。 それ以外の場合、return およびref が任意の位置にある場合は、ReturnRef を意味する。
Example | Classification | Comments |
---|---|---|
X foo(P p) | なし | |
X foo(scope P p) | スコープ | |
P foo(return scope P p) | ReturnScope | |
I foo(return scope P p) | スコープ | return は、戻り値の型I がポインタを含んでいないため、削除される。 |
P foo(return P p) | ReturnScope | scope なしでreturn を持つ意味はない。 |
I foo(return P p) | スコープ | 戻り値の型I にポインタが含まれていないため、return は削除される。 |
X foo(ref P p) | 参照 | |
X foo(ref scope P p) | RefScope | |
P foo(ref return scope P p) | Ref-ReturnScope | |
P foo(return ref scope P p) | ReturnRef-Scope | |
I foo(ref return scope P p) | RefScope | |
P foo(ref return P p) | ReturnRef | |
I foo(ref return P p) | Ref | |
ref X foo(P p) | なし | |
ref X foo(scope P p) | スコープ | |
ref X foo(return scope P p) | ReturnScope | |
ref X foo(return P p) | ReturnScope | scope なしでreturn を持つ意味はない。 |
ref X foo(ref P p) | 参照 | |
ref X foo(ref scope P p) | RefScope | |
ref X foo(ref return scope P p) | Ref-ReturnScope | |
ref X foo(return ref scope P p) | ReturnRef-Scope | |
ref X foo(ref return P p) | ReturnRef | |
X foo(I i) | なし | |
X foo(scope I i) | なし | |
X foo(return scope I i) | なし | |
X foo(return I i) | なし | |
X foo(ref I i) | 参照 | |
X foo(ref scope I i) | Ref | |
X foo(ref return scope I i) | リターン | |
P foo(ref return I i) | リターン参照 | |
I foo(ref return I i) | Ref | |
ref X foo(I i) | なし | |
ref X foo(scope I i) | なし | |
ref X foo(return scope I i) | なし | |
ref X foo(return I i) | なし | |
ref X foo(ref I i) | 参照 | |
ref X foo(ref scope I i) | 参照 | |
ref X foo(ref return scope I i) | ReturnRef | |
ref X foo(ref return I i) | ReturnRef |
メンバー関数
メンバー関数は、this パラメータが非メンバー関数の最初のパラメータであるかのように書き換えられる。
struct S {
X foo();
}
次のように扱われます。
X foo(ref S);
そして:
class C {
X foo()
}
次のように扱われる。
X foo(P)
Pおよび参照
ref とPの切り替えについては、次のようなルールがある。
int* foo(return ref int i) { return &i; } ref int foo(int* p) { return *p; }
共分散
共分散とは、制約のあるパラメータを、より制約の少ないパラメータに変換できることを意味する。 これは、各状態の説明から推論できる。
From\To | None | ReturnScope | Scope |
---|---|---|---|
なし | ✔ | ||
ReturnScope | ✔ | ✔ | |
スコープ | ✔ | ✔ | ✔ |
From\To | Ref | ReturnRef | RefScope | ReturnRef-Scope | Ref-ReturnScope |
---|---|---|---|---|---|
参照 | ✔ | ✔ | |||
ReturnRef | ✔ | ||||
RefScope | ✔ | ✔ | ✔ | ✔ | ✔ |
ReturnRef-Scope | ✔ | ✔ | |||
Ref-ReturnScope | ✔ | ✔ | ✔ |
例えば、scope はすべての非参照パラメータに一致し、ref scope はすべての参照パラメータに一致する。
xml-ph-0000@deepl.internalローカル変数
ローカル変数は関数のスコープ内で宣言される。 関数パラメータが含まれる。
ローカル変数は、まず値を割り当てないと読み取ることができない。
ローカルの非静的変数のアドレスまたは参照は、 関数から返すことができない。
同じ関数内のローカル変数とラベルに同じ名前を付けることはできない。
ローカル変数は、同じ関数内の別のローカル変数を隠すことはできない。
ref double func(int x) { int x; // エラー、xの前の定義を隠す double y; { char y; // エラー、以前のyの定義を隠す int z; } { wchar z; // OK、前のzはスコープ外である } z: // エラー、zはローカル変数でありラベルである return y; // エラー、ローカルへのrefを返す }emailemail
ローカル静的変数
static 、shared static、または__gshared として宣言された関数内のローカル変数は、 スタック上に割り当てられるのではなく、静的に割り当てられる。__gshared およびshared static 変数の有効期間は、 関数が最初に実行されたときに開始し、プログラムが終了したときに終了する。static 変数の有効期間は、関数がスレッド内で最初に実行されたときに開始し、 そのスレッドが終了したときに終了する。
import std.stdio : writeln; void foo() { static int n; if (++n == 100) writeln("called 100 times"); }
静的変数の初期化子は、コンパイル時に評価可能でなければならない。 静的ローカル変数には、静的コンストラクタや静的デストラクタは存在しない。
静的変数の名前の可視性は通常のスコープのルールに従うが、 それらの名前は特定の関数内で一意でなければならない。
void main() { { static int x; } { static int x; } // エラー { int i; } { int i; } // OK }
ネストされた関数
関数は他の関数の中にネストされる場合がある。
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は未定義 }and:
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; // bに4が追加される 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); }
静的ネスト関数は、 語彙的に囲む関数のスタック変数にはアクセスできないが、静的変数にはアクセスできる。 これは、静的メンバ関数の動作に類似している。
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; } }
ネストされた関数は、常にD関数リンケージ型を持つ。
宣言順序
モジュールレベルの宣言とは異なり、関数スコープ内の宣言は 順番に処理される。つまり、2つのネストされた関数は 相互に呼び出すことができない。
void test() { void foo() { bar(); } // エラー、barが定義されていない void bar() { foo(); } // OK }
この制限に対するいくつかの回避策がある。
- 関数をネストされた構造体の static メンバとして宣言する:
void test() { static struct S { static void foo() { bar(); } // OK static void bar() { foo(); } // OK } S.foo(); // コンパイルする(ただし、無限の実行時ループに注意) }
void test() { void foo()() { bar(); } // OK(fpは関数テンプレートへのポインタである) void bar() { foo(); } // OK }
mixin template T() { void foo() { bar(); } // OK void bar() { foo(); } // OK } void main() { mixin T!(); }
void test() { void delegate() fp; void foo() { fp(); } void bar() { foo(); } fp = &bar; }
ネストされた関数はオーバーロードできない。
関数ポインタ、デリゲート、クロージャ
関数ポインタ
関数ポインタは、function キーワードで宣言される。
void f(int); void function(int) fp = &f; // fpはintを取る関数へのポインタである
関数ポインタは静的ネスト関数を指すことができる。
int function() fp; // fpはintを返す関数へのポインタである void test() { static int a = 7; static int foo() { return a + 3; } fp = &foo; } void main() { assert(!fp); test(); int i = fp(); assert(i == 10); }
int abc(int x) { return x + 1; } uint def(uint y) { return y + 1; } int function(int) fp1 = &abc; uint function(uint) fp2 = &def; // fp1とfp2が異なる値であることを当てにしてはならない; コンパイラは // それらをマージするかもしれない。
デリゲートとクロージャ
非静的ネスト関数にデリゲートを設定できる。
int delegate() dg; void test() { int a = 7; int foo() { return a + 3; } dg = &foo; int i = dg(); // iを10に設定 } void main() { test(); int i = dg(); // OK、test.aはクロージャ内にあり、まだ存在する assert(i == 10); }
ネストされた関数によって参照されるスタック変数は、 関数が終了した後も有効である(注釈:これは D 1.0とは異なる)。 この環境と関数の組み合わせは、
クロージャを構成する参照スタック変数は、 次の場合を除いて、GCヒープ上に割り当てられる。
- クロージャがscope パラメータに渡される場合。
- クロージャがscope 変数の初期化子である場合。
- クロージャがscope 変数に代入されている。
@nogc: void f(scope int delegate()); void g(int delegate()); void main() { int i; int h() { return i; } h(); // OK scope x = &h; // OK x(); // OK //auto y = &h; // エラー、@nogc関数でクロージャを確保できない f(&h); // OK //g(&h); // エラー // デリゲート・リテラル f(() => i); // OK scope d = () => i; // OK d = () => i + 1; // OK f(d); //g(() => i); // エラー、@nogc関数でクロージャを割り当てられない }
メソッドデリゲート
非静的ネスト関数へのデリゲートには、2つのデータが含まれる 。すなわち、語彙的に囲む関数のスタックフレームへのポインタ (コンテキストポインタと呼ばれる)と、関数のアドレスである。 これは、"this"ポインタと メンバ関数のアドレスで構成される、構造体/クラス非静的メンバ関数デリゲートに類似している。 両方の形式のデリゲートは区別できず、 同じ型である。
特定のオブジェクトとメソッドにデリゲートを設定するには、&obj.method を使用する。
struct Foo { int a; int get() { return a; } } int add1(int delegate() dg) { return dg() + 1; } void main() { Foo f = {7}; int delegate() dg = &f.get; // Fooのインスタンスとメソッドにバインドする assert(dg.ptr == &f); assert(dg.funcptr == &Foo.get); int i = add1(dg); assert(i == 8); int x = 27; int abc() { return x; } i = add1(&abc); assert(i == 28); }
デリゲートの.ptr プロパティは、 コンテキストポインタの値をvoid* として返す。
デリゲートの.funcptr プロパティは、 関数ポインタ値を関数型として返す。
初期化
関数ポインタはデフォルトでゼロ初期化される。 任意の関数(関数リテラルを含む)のアドレスに初期化することもできる。 コンテキストポインタを必要とする関数のアドレスによる初期化は、 @safe関数では許可されない。
struct S { static int sfunc(); int member(); // 参照パラメータが`this`に隠されている } @safe void sun() { int function() fp = &S.sfunc; fp(); // OK fp = &S.member; // エラー } @system void moon() { int function() fp = &S.member; // OK、because @system fp(); // 未定義の動作 }
デフォルトでは、デリゲートはゼロで初期化される。 非staticメンバ関数のアドレスを取得することで初期化することもできるが、 その場合はコンテキストポインタを指定する必要がある。 非staticネスト関数または関数リテラルのアドレスを取得することで初期化することもできる。 この場合、コンテキストポインタはスタックフレーム、クロージャ、null を指すように設定される。
グローバル関数、"staticメンバ関数"、"staticネスト関数"のアドレスを取得して、デリゲートを初期化することはできない。 xml-ph-0000@deepl.internal
struct S { static int sfunc(); int member() { return 1; } } void main() { S s; int delegate() dg = &s.member; // OK、sはコンテキストポインタを提供する assert(dg() == 1); //dg = &S.sfunc; // エラー //dg = &S.member; // エラー int moon() { return 2; } dg = &moon; // OK assert(dg() == 2); static int mars() { return 3; } //dg = &mars; // エラー dg = () { return 4; }; // OK assert(dg() == 4); }
最後の代入ではFunctionLiteral を使用しており、これは デリゲートとして推論される 。
匿名関数と匿名デリゲート
FunctionLiteralsを参照。
main() 関数
コンソールプログラムでは、main() がエントリーポイントとして機能する。 これは、すべてのモジュール初期化が実行された後、 およびすべてのユニットテストが実行された後に呼び出される。 これが返された後、すべてのモジュールデストラクタが実行される。main() は、以下のように宣言しなければならない。
MainFunction: MainReturnDecl main() MainFunctionBody MainReturnDecl main(string[] Identifier) MainFunctionBody MainReturnDecl: void int noreturn auto MainFunctionBody: ShortenedFunctionBody SpecifiedFunctionBody
- main がvoid を返した場合、OSは成功時にゼロ値を受け取る。
- main がvoid またはnoreturn を返した場合、OSは 例外が発生するなどして異常終了した場合にゼロ以外の値を受け取る。
- main がauto として宣言されている場合、推論される戻り値の型はvoid 、int 、noreturn のいずれかでなければならない。
string[] パラメータが宣言されている場合、そのパラメータには OSからプログラムに渡された引数が格納される。最初の引数は通常、 実行可能ファイル名であり、それにコマンドライン引数が続く。
主関数はDリンケージでなければならない。
必要に応じて属性を追加できる。例えば、@safe 、@nogc 、nothrow など。
extern(C) main() 関数
プログラムは、extern(C) main 関数を標準のエントリーポイントの代替として定義できる。 この形式は、 BetterC。
Cmain 関数は以下のように宣言しなければならない。
CMainFunction: extern (C) MainReturnDecl main(CmainParametersopt) BlockStatement CmainParameters: int Identifier, char** Identifier int Identifier, char** Identifier, char** Identifier
定義された場合、最初の2つのパラメータは、OSからプログラムに渡された引数を保持する"C言語のスタイル"の配列(長さ+ポインタ)を意味する。 3番目のパラメータは、POSIX 拡張機能であるenviron と呼ばれ、現在の環境変数に関する情報を保持する。
この関数は C の main 関数の代わりとなり、 Dmain 関数に関連するセットアップやテアダウンなしですぐに実行される。モジュール コンストラクタ、モジュールデストラクタ、または unittests に依存するプログラムでは、 適切なランタイム関数を使用して (デ) 初期化を手動で実行する必要がある。
pragma(mangle, "main") int myMain(int a, int b, int c) { return 0; }
関数テンプレート
関数には、テンプレート形式のコンパイル時の引数を指定できる。 関数テンプレートを参照。
functionfunctionコンパイル時関数実行(CTFE)
コンパイル時の値が必要なコンテキストでは、 関数を使用してこれらの値を計算することができる。これをコンパイル時関数実行 (CTFE)と呼ぶ。
これらのコンテキストは:
- 静的変数またはマニフェスト定数の初期化
- 構造体/クラスメンバーの静的初期化子
- 静的配列の次元
- テンプレート値パラメータの引数
- static if
- static foreach
- static assert
- mixin 文
- pragma 引数
- __traits 引数
enum eval(alias arg) = arg; int square(int i) { return i * i; } void main() { import std.stdio; static j = square(3); // CTFE writeln(j); assert(square(4) == 16); // 実行時 static assert(square(3) == 9); // CTFE writeln(eval!(square(5))); // CTFE }
関数にはSpecifiedFunctionBody がなければならない。
CTFEには以下の制限がある。
- 式はグローバルまたはローカルの静的変数を参照できない。
- アセンブリステートメントは許可されていない
- 非ポータブルキャスト(例えば、int[] からfloat[] へのキャストなど)は許可されない。 エンディアンに依存するキャストも許可されない。 符号付き型と符号なし型の間のキャストは許可される。
- 共用体で重複するフィールドの再解釈は許可されない。
CTFEでは、安全に使用される限り、ポインタは許可される。
- ポインタの算術演算は、静的配列要素または動的配列要素を指すポインタに対してのみ許可される。 ポインタは配列の最初の要素を越えて指すこともできるが、 そのようなポインタは参照解除できない。 nullであるポインタ、または配列以外のものを指すポインタに対するポインタの算術演算は
- 2つのポインタ間の順序付き比較(< 、< 、= 、> 、>= )は、 両方のポインタが同じ配列を指している場合、または少なくとも1つのポインタがnull の場合、許可される。
- 不連続なメモリブロック間のポインタ比較は、 && または|| を使用して、 メモリブロックの順序に依存しない結果を生成するように、2つの比較を組み合わせない限り、 無効である。各比較は、2つのポインタ式で構成され、 < 、<= 、> 、 または>= と比較され、オプションで ! で否定される場合がある。 例えば、(p1 > q1 && p2 <= q2)という式は、 p1 、p2 がメモリブロックPへのポインタを生成する式であり、 、 が q1 、q2 が メモリブロックQへのポインタを生成する式である場合、PとQが 無関係なメモリブロックであっても 、[p1..p2] が[q1..q2] の範囲内にある場合はtrueを返し、それ以外はfalseを返す。 同様に、(p1 < q1 || p2 > q2) は [p1..p2] が[q1..q2] の範囲外にある場合はtrueを返し、それ以外はfalseを返す。
- 等価比較(==、!=、is 、!is)は、 制限なくすべてのポインタ間で許可される。
- すべてのポインタはvoid* にキャストでき、void* から元の型に戻すこともできる。 ポインタ型と非ポインタ型の間のキャストは 違法である。
上記の制限は、実際に実行される式のみに適用される。 例えば:
static int y = 0; int countTen(int x) { if (x > 10) ++y; // 静的変数にアクセスする return x; } static assert(countTen(6) == 6); // OK static assert(countTen(12) == 12); // 無効、yを変更する。
__ctfe ブール値の疑似変数は、CTFE 中はtrueと評価されるが それ以外ではfalseと評価される。
回復不能なエラー(アサートエラーなど)は不正である。
文字列ミックスインとコンパイル時の関数実行
CTFEで実行されるすべての関数は、 実行時にも実行可能でなければならない。関数のコンパイル時評価は、 実行時にその関数を実行することと等価である。関数のセマンティクスは、 その関数のコンパイル時値に依存することはできない。例えば:
int foo(string s) { return mixin(s); } const int x = foo("1");は無効である。なぜなら、foo の実行時コードを生成できないからだ 。
No-GC Functions
No-GC関数は、@nogc 属性でマークされた関数である。 これらの関数は、GCヒープ上にメモリを割り当てない。 No-GC関数では、以下の操作は許可されていない。
- ヒープ上に配列を構築する
- .length プロパティ配列への書き込みによる配列のサイズ変更
- 配列の連結
- 配列の追加
- 連想配列の作成
- 連想配列の添字付け
注釈: 指定したキーが存在しない場合はRangeError がスローされる可能性があるため、
- ヒープ上に new でオブジェクトを割り当てる
注釈: 関数スコープ内でのclass types のnew 宣言は、scope 変数に使用される場合、スタック上に割り当てが行われるため、@nogc と互換性がある
- @nogc 以外の関数を呼び出すことになるため、 DebugConditionによって制御されるConditionalStatement 内での呼び出しでない限り、
@nogc void foo() { auto a = ['a']; // (1) エラー、allocates a.length = 1; // (2) エラー、array resizing allocates a = a ~ a; // (3) エラー、arrays concatenation allocates a ~= 'c'; // (4) エラー、appending to arrays allocates auto aa = ["x":1]; // (5) エラー、allocates aa["abc"]; // (6) エラー、indexing may allocate and throws auto p = new int; // (7) エラー、operator new allocates scope auto p = new GenericClass(); // (7) OK bar(); // (8) エラー、bar() may allocate debug bar(); // (8) OK } void bar() { }
No-GC関数では、クロージャを使用できるのは、scope の場合のみである。 Delegates & Closuresを参照。
@nogc int delegate() foo() { int n; // エラー、変数nはヒープ上に確保できない return (){ return n; } // `n`は`foo()`をエスケープするので、クロージャが必要である }
@nogc 関数の型に影響する。 関数は、 関数と共変する。 @nogc@nogc
void function() fp; void function() @nogc gp; // @nogc関数へのポインタ void foo(); @nogc void bar(); void test() { fp = &foo; // OK fp = &bar; // OK、共変数だ gp = &foo; // エラー、not contravariant gp = &bar; // OK }
関数安全
安全関数
安全関数は、@safe 属性でマークされている。@safe は推論できる。 関数属性の推論を参照。
安全な関数には安全なインターフェイスがある。 実装では、 関数の本体を安全であることが分かっている操作に制限することで、@trusted 関数への呼び出しを除いて、これを強制しなければならない。
以下の制限は、コンパイラによって安全な関数で強制される。
- ポインタ型T からポインタ型U へのキャストは、次の場合を除いて許可されない。
- T 暗黙的に変換する場合、U
- U クラスまたはインターフェースを実装する場合T
- 両方の型が動的配列である
- T.opCast!U @safe
- ポインタ型以外の型からポインタ型へのキャストは行わない。
- ポインタ演算(ポインタのインデックスを含む)はできない。
- 次の共用体にはアクセスできない。
- 他の型と重複するポインタまたは参照を持つ
- 不変フィールドが他の型と重複している
- システム関数を呼び出す。
- 派生していない例外の捕捉はできない class Exception。
- インラインアセンブラなし。
- 明示的なキャストは不可:
- 変更可能なオブジェクトを変更不可能なオブジェクトに
- 変更不可能なオブジェクトを変更可能にする
- スレッドローカルオブジェクトを共有オブジェクトに
- 共有オブジェクトをスレッドローカルに
- 変数にアクセスできない @system または__gshared 変数にアクセスできない。
- void 初期化子を使用できない。
- ポインタ/参照型、またはそれらを含む任意の型
- 不変条件を持つ型
安全な関数の中にネストされた関数は、デフォルトで安全な関数となる。
安全な関数は、信頼された関数やシステム関数と共変する。
安全な外部関数
外部関数には、コンパイラから見える関数本体がない。
@safe extern (C) void play();そのため、安全性を自動的に検証することはできません。
信頼できる関数
信頼された関数は、@trusted 属性でマークされている。
安全な関数と同様に、信頼された 関数も安全なインターフェースを持つ。 安全な関数とは異なり、これは関数本体の制限によって強制されるものではない。 代わりに、信頼された関数のインターフェースが安全であることを保証するのはプログラマーの責任である
例:
immutable(int)* f(int* p) @trusted { version (none) p[2] = 13; // p[2]は範囲外。 // この行は未定義の動作を示す。 version (none) p[1] = 13; // 無効。このプログラムでは、p[1]はたまたま境界内なので、 // この行は未定義の振る舞いを示すことはないが、 // 信頼された関数はこれに依存することは許されない。 version (none) return cast(immutable) p; // 無効。@safeコードはまだ変更可能なアクセスを持っており、 // 後で値を上書きすることによって未定義の振る舞いを引き起こす可能性がある。 int* p2 = new int; *p2 = 42; return cast(immutable) p2; // 有効。fが返却値を返した後、p2の変更可能なエイリアスは存在できない。 } void main() @safe { int[2] a = [10, 20]; int* mp = &a[0]; immutable(int)* ip = f(mp); assert(a[1] == 20); // Guaranteed。fはa[1]にアクセスできない。 assert(ip !is mp); // Guaranteed。fは安全でないエイリアシングを導入できない。 }
信頼された関数は、安全な関数、信頼された関数、またはシステム関数を呼び出すことができる。
信頼された関数は、安全な関数またはシステム関数と共変である。
システム関数
システム関数は、@safe または@trustedのマークが付いておらず、 @safe 関数の内部にネストされていない関数である。 システム関数は、@system 属性が付いている場合がある。 システム関数であるということは、その関数が実際に安全でないことを意味するのではなく、 その安全性を手動で検証する必要があることを意味する。
システム関数は、信頼された関数や安全な関数と共変しない。
システム関数は、安全関数および信頼済み関数を呼び出すことができる。
安全なインターフェース
関数呼び出しの引数、コンテキスト、 アクセス可能なグローバル変数のそれぞれが安全な値を持ち、 安全なエイリアシングが適用されている場合、 その関数は安全なインターフェースを持つ。
未定義の動作を示すことが できず、 かつ- 未定義の動作を示すことがなく 、 かつ
- @safe のコードからアクセス可能な安全でない値を生成することができない (例えば、戻り値、グローバル変数、ref パラメータを介してなど)、
- @safe コードからアクセス可能な安全でないエイリアシングを導入することはできない。
これらの要件を満たす関数は、 @safeまたは @trusted。これらの要件を満たさない関数は、 @system。
例:
- Cのfree は安全なインターフェイスを持っていない。なぜなら、
extern (C) @system void free(void* ptr);
なぜなら、free(p) がp を無効化し、その値を安全でないものにするからである。free は@system であるにすぎない。 - Cのstrlen とmemcpy は安全なインターフェイスを持たない。なぜなら、
extern (C) @system size_t strlen(char* s); extern (C) @system void* memcpy(void* dst, void* src, size_t nbytes);
なぜなら、それらは検証されていない仮定に基づいてポインタを反復するからである (strlen はs がゼロ終端文字列であると仮定し、memcpy は dst およびsrc が指すメモリオブジェクトが少なくともnbytes バイトであると仮定する)。 引数として渡されたC文字列を走査する関数は、すべて @system である。 配列境界を別のパラメータとして信頼する関数は、すべて@system である。 - Cのmalloc は安全なインターフェイスを備えている。
extern (C) @trusted void* malloc(size_t sz);
あらゆる入力に対して未定義の動作を示すことはない。 有効なポインタを返すか、null を返す。 も 安全である。注釈:malloc の実装は、おそらく@systemコードである。 - memcpy のDバージョンは安全なインターフェースを持つことができる。
@safe void memcpy(E)(E[] src, E[] dst) { import std.math : min; foreach (i; 0 .. min(src.length, dst.length)) { dst[i] = src[i]; } }
安全な値
bool では、0と1のみが安全な値である。
その他のすべての基本データ型については、 すべての可能なビットパターンが安全である。
ポインタは、以下のいずれかの場合は安全な値である。
- null
- 有効なメモリオブジェクトを指しており、 そのメモリオブジェクト内の指し示された値が安全である場合、
例:
xml-ph-0000@deepl.internalint* n = null; /* nullの再参照はクラッシュとして定義されているので、 nは安全である。 */ int* x = cast(int*) 0xDEADBEEF; /* xは有効なポインタではなく、 参照解除できないので、(ほとんどの場合)安全ではない。 */ import core.stdc.stdlib: malloc, free; int* p1 = cast(int*) malloc(int.sizeof); /* p1はポインタが有効なので安全であり、 *p1は実際の値に関係なく安全である。 */ free(p1); /* これでp1は安全ではない。 */ int** p2 = &p1; /* それは参照解除できるが、 p1が安全ではないのでp2は安全ではない。 */ p1 = null; /* これでp1とp2は安全である。 */
動的配列は、以下の条件を満たす場合に安全である。
- ポインタが安全であり、
- その長さが対応するメモリオブジェクトの範囲内であり、 そして
- すべての要素が安全である。
例:
xml-ph-0000@deepl.internalint[] f() @system { bool b = true; /* bは安全に初期化される */ *(cast(ubyte*) &b) = 0xAA; /* bは0でも1でもないので安全ではない */ int[3] a; int[] d1 = a[0 .. 3]; /* d1は安全である。 */ int[] d2 = a.ptr[0 .. 4]; /* d2はaの境界を超えるので 安全ではない。 */ int*[] d3 = [cast(int*) 0xDEADBEEF]; /* d3は要素が安全でないので 安全でない。 */ return d1; /* ここまではd1は安全であったが、関数が戻ると ポインタが無効になるため、返される動的配列は 安全でない。 */ }
静的配列は、すべての要素が安全である場合に安全である。 要素の型に関係なく、長さがゼロの静的配列は常に安全である。
連想配列は、そのキーと要素がすべて安全である場合に安全である。
構造体/共用体のインスタンスは、以下の条件を満たす場合に安全である。
- アクセス可能なフィールドの値が安全であり、
- 共用体を使用して安全でないエイリアスを導入しない。
例:
void fun() { struct S { int* p; } S s1 = S(new int); /* s1は安全である。 */ S s2 = S(cast(int*) 0xDEADBEEF); /* s2は安全ではない。なぜならs2.pは 安全ではないからである。 */ union U { int* p; size_t x; } U u = U(new int); /* u.pもu.xも安全であるにもかかわらず、 uは安全でないエイリアシングのために安全でない。 */ }
クラス参照は、null または: の場合、安全である。
- そのクラス型の有効なクラスインスタンス、または そのクラス型から派生した型を参照しており、
- インスタンスのアクセス可能なフィールドの値が安全であり、
- 共用体との間で安全でないエイリアシングを導入しない。
関数ポインタは、それがnull であるか、または 同じか上位互換のシグネチャを持つ有効な関数を参照している場合は安全である。
delegate が安全であるのは、以下の場合である。
そのxml-ph-0000@deepl.internalプロパティがxml-ph-0001@deepl.internalであるか、 またはデリゲート型と一致するか、またはそれと共変する関数を参照している- その.funcptr プロパティがnull であるか、または デリゲート型と一致するか、またはそれと共変する関数を参照しており、
- その.ptr プロパティがnull であるか、または関数で期待される形式のメモリオブジェクトを参照している 。
安全なエイリアス
1つのメモリロケーションが2つの異なる型でアクセス可能な場合、 そのエイリアスは安全であるとみなされる。
両方のタイプがxml-ph-0000@deepl.internalまたはxml-ph-0001@deepl.internalである場合、または、- 両方の型がconst またはimmutable である場合、または
- 一方の型が変更可能で、もう一方がconst 修飾された 基本データ型である場合、または
- 両方の型が変更可能な基本データ型である場合、または
- 型のいずれかが長さゼロの静的配列型である場合、または
- いずれかの型が非ゼロ長の静的配列型であり、 配列の要素型と他の型のエイリアシングが安全である場合、または
- 両方の型がポインタ型であり、対象の型のエイリアシングが 安全であり、対象の型が同じサイズである。
その他のすべてのケースのエイリアシングは、安全ではないとみなされる。
例:
void f1(ref ubyte x, ref float y) @safe { x = 0; y = float.init; } union U1 { ubyte x; float y; } // 安全なエイリアシング void test1() { U1 u1; f1(u1.x, u1.y); // OK } void f2(ref int* x, ref int y) @trusted { x = new int; y = 0xDEADBEEF; } union U2 { int* x; int y; } // 安全でないエイリアシング void test2() { U2 u2; version (none) f1(u2.x, u2.y); // 安全ではない }
関数属性推論
関数リテラル、 "auto関数"、 "auto Ref関数"、 "ネストされた関数"、 "関数テンプレート"は、 関数本体が常に存在するため、
他の関数については、関数本体が存在する場合でも属性の推論は行われない。
関数本体が特定の属性のルールに従っているかどうかを判断することで、推論が行われる。
循環関数(すなわち、直接または間接的に自身を呼び出す関数)は、"純粋でない"、"スローする"、@system と推論される。
関数がそれらの属性について自身をテストしようとする場合、 その関数はそれらの属性を持たないと推測される。
Uniform Function Call Syntax (UFCS)
無料の関数は、以下の両方の条件を満たす場合、メンバ関数のように呼び出すことができる。
オブジェクト式に対してメンバ関数が存在しない(または存在できない)- メンバ関数がオブジェクト式に対して存在しない(または存在できない)場合
- フリー関数の第1パラメータの型がオブジェクト式と一致する
オブジェクト式は任意の型でよい。 これはUFCS関数呼び出しと呼ばれる。
void sun(T, int); void moon(T t) { t.sun(1); // `T`にメンバ関数`sun`がない場合、 // `t.sun(1)`は`sun(t, 1)`と書かれたものとして解釈される。 }
より複雑な例:
stdin.byLine(KeepTerminator.yes) .map!(a => a.idup) .array .sort .copy(stdout.lockingTextWriter());
は次のものと同じ意味である。
copy(sort(array(map!(a => a.idup)(byLine(stdin, KeepTerminator.yes)))), lockingTextWriter(stdout));
UFCSは@property 関数と連携する。
@property prop(X thisObj); @property prop(X thisObj, int value); X obj; obj.prop; // Xがメンバ関数propを持たない場合、prop(obj);と解釈する obj.prop = 1; // 同様に、prop(obj, 1);と解釈する
ローカルスコープで宣言された関数は、一致するUFCS関数を探す際に検出されない。 ローカルインポートは検索されるが、他のローカルシンボルも検出されない。
module a; void foo(X); alias boo = foo; void main() { void bar(X); // ローカルのスコープで宣言された import b : baz; // void baz(X); X obj; obj.foo(); // OK、a.foo;を呼び出す //obj.bar(); // NG、UFCSはネストされた関数を見ない obj.baz(); // OK、モジュールbのトップレベルのスコープで宣言されているので、 // b.bazを呼び出す import b : boo = baz; obj.boo(); // OK、a.boo (== a.foo)の代わりにエイリアスのb.bazを呼び出す。 // なぜなら、ローカルスコープで宣言されたエイリアス名'boo'が // モジュールスコープ名を上書きするからである }
UFCS関数に一致するものを検索しても、メンバ関数は見つからない。
class C { void mfoo(X); // メンバ関数 static void sbar(X); // 静的メンバ関数 import b : ibaz = baz; // void baz(X); void test() { X obj; //obj.mfoo(); // NG、UFCSはメンバ関数を見ない //obj.sbar(); // NG、UFCSは静的メンバ関数を見ない obj.ibaz(); // OK、ibazはモジュールbのトップレベルスコープで宣言された // bazのエイリアスです } }
それ以外の場合、UFCS関数の検索は通常通り行われる。
int front(int[] arr) { return arr[0]; } void main() { int[] a = [1,2,3]; auto x = a.front(); // UFCSは.frontを呼び出す auto front = x; // frontは変数になる auto y = a.front(); // エラー、frontは関数ではない } class C { int[] arr; int front() { return arr.front(); // エラー、C.frontは呼び出し可能ではない // 引数型(int[])を使用している } }
DEEPL APIにより翻訳、ところどころ修正。
このページの最新版(英語)
このページの原文(英語)
翻訳時のdmdのバージョン: 2.109.1
ドキュメントのdmdのバージョン: 2.109.1
翻訳日付 :
HTML生成日時:
編集者: dokutoku