英語版
このページの英語版を見る

関数

目次
  1. 関数宣言
    1. 関数本体
    2. 関数本体のない関数
  2. 関数の契約
    1. 前提条件
    2. 事後条件
    3. イン、アウト、継承
  3. 関数戻り値
  4. 純粋関数
    1. 強い純粋度と弱い純粋度
    2. 特殊なケース
    3. 純粋な工場関数
    4. 最適化
  5. nothrow 関数
  6. Ref関数
  7. 自動関数
  8. 自動参照関数
  9. オプションの括弧
  10. オプションの括弧
  11. プロパティ関数
  12. 仮想関数
    1. 共変
    2. 基底クラスのメソッドの呼び出し
    3. オーバーロード セットとデフォルト値のオーバーライド
    4. デフォルト値
    5. 継承された属性
    6. 制限
  13. インライン関数
  14. 関数のオーバーロード
    1. オーバーロードセット
  15. 関数パラメータ
    1. パラメータの保存クラス
    2. パラメータ内
    3. 参照および出力パラメータ
    4. 遅延パラメータ
    5. デフォルト引数
    6. 参照パラメータを返す
    7. スコープパラメータ
    8. 戻り値スコープパラメータ
    9. 参照値を返すスコープパラメータ
    10. pure 関数におけるscope パラメータの推論
    11. パラメータのユーザー定義属性
    12. 可変長引数関数
    13. 隠されたパラメータ
  16. 参照スコープ リターンケース
    1. 定義
    2. 分類
    3. マッピング構文分類
    4. メンバ関数
    5. Pと参照
    6. 共分散
  17. ローカル変数
    1. ローカル静的変数
  18. ネストされた関数
    1. 宣言順序
  19. 関数ポインタ、デリゲート、クロージャ
    1. 関数ポインタ
    2. デリゲートとクロージャ
    3. 初期化
    4. 匿名関数と匿名デリゲート
  20. main() 関数
    1. extern(C) main() 関数
  21. 関数テンプレート
  22. コンパイル時関数実行(CTFE)
    1. 文字列ミックスインとコンパイル時関数実行
  23. GCなし関数
  24. 関数安全性
    1. 安全な関数
    2. 信頼された関数
    3. システム関数
    4. 安全なインターフェース
    5. 安全な値
    6. セーフエイリアシング
  25. 関数属性推論
  26. Uniform Function Call Syntax (UFCS)

関数宣言

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
注釈: D2では、パラメータfinal を宣言することは意味エラーであるが、構文エラーではない。

参照:パラメータストレージクラス

関数属性

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となる。

前提条件は、関数の実行開始前に意味論的に満たされていなければならない。 満たされていない場合、プログラムは無効な状態に入る。

Implementation Defined: 前提条件が実際に実行されるかどうかは、処理系定義による。 これは通常、コンパイラのスイッチで選択可能である。 前提条件が満たされない場合の動作も、通常はコンパイラのスイッチで選択可能である。 オプションの2番目の AssignExpression で構成されるメッセージとともに をスローする、という選択肢もある AssertError
Best Practices: 前提条件を使用して、入力引数にその関数で期待される値が設定されていることを検証する。
Best Practices: 前提条件は実行時に実際にチェックされる場合もされない場合もあるため、 副作用のある前提条件の使用は避けるべきである。 xml-ph-0000@deepl.internal

式の形式は次のとおりである。

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となる。

関数の実行が終了した後に、意味論的に後件が満たされる必要がある。 満たされない場合、プログラムは無効な状態になる。

Implementation Defined: ポスト条件が実際に実行されるかどうかは処理系定義である。 これは通常、コンパイラのスイッチで選択可能である。 ポスト条件の失敗時の動作も通常、コンパイラのスイッチで選択可能である。 オプションの2番目の AssignExpression で構成されるメッセージとともに をスローする、という選択肢もある AssertError
Best Practices: ポスト条件を使用して、入力引数と戻り値が関数で期待される値であることを検証する。
Best Practices: 実行時に実際にチェックされる場合もされない場合もあるため、 副作用のあるポストコンディションの使用は避けるべきである。 xml-ph-0000@deepl.internal

式の形式は次のとおりである。

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 文が必要である。 ただし、

関数の戻り値にマークがない場合、 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;
}

純度の高い関数は、純度の低い関数を呼び出すことができる。

特別な場合

純粋関数は、

Undefined Behavior: 関数終了時にこれらのフラグが初期状態に復元されていない場合、 が発生する。これを確実に実行することはプログラマーの責任である。 これらのフラグの設定は、 コードでは許可されていない。@safe

デバッグ

純粋関数は、 ConditionalStatement がDebugConditionによって制御されている文において、"純粋でない"操作を実行できる。

Best Practices: このDebugConditionsにおける純粋性のチェックの緩和は、 プログラムのデバッグを容易にすることを唯一の目的としている。 xml-ph-0000@deepl.internal
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オブジェクトを参照しており、プログラムの他の部分ではこれらのオブジェクトを参照していない。 したがって、結果は不変の変数を初期化できる。

これは関数からスローされる例外エラーには影響しない。

最適化

Implementation Defined: 実装では、強く純粋な 関数が、変更不可能な間接参照のみを持つ(または間接参照を持たない)引数で呼び出され 、変更可能な間接参照を持たない結果を返す 場合、同等の引数を持つすべての呼び出しに対して同じ効果を持つと仮定してもよい。 同等の引数は常に同等の結果を生成するという仮定の下で、関数の結果をメモ化することは許可される 。
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と推測される
注釈: 戻り値の型推論は、 属性の推論も引き起こす。
to be voidトリガー

自動参照関数

自動参照関数は、"auto関数"と同様に返り値の型を推論できる。 さらに、以下のすべてに該当する場合は参照関数となる

関数の参照性は、関数本体のすべての 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);
}
xml-ph-0000@deepl.internal
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 属性を追加することで拡張でき、これにより、 以下の動作が追加される。

単純なプロパティは次のようになる。

xml-ph-0000@deepl.internal
struct 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メソッド属性は、 サブクラスによるメソッドのオーバーライドを禁止する。

以下は仮想ではない。

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参照を通じて呼び出すこともできます。

Implementation Defined: 通常、仮想関数を呼び出すということは、 クラスの にインデックスを指定して、実行時にその関数のアドレスを取得することを意味する。 仮に、呼び出される仮想関数が静的に既知であると実装側で判断できる場合 (例えば、 の場合など)は、直接呼び出しを使用することができる。 vtbl[] final

オーバーロードセットとオーバーライド

オーバーロード解決を行う際には、ベースクラスの関数は考慮されない。 これは、それらが同じ オーバーロードセットに属さないためである。

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 オーバーライド関数では許可されていない。

Rationale: コンパイルを停止したり、非推奨メッセージを出力したりするには、実装は 呼び出しの対象を決定できなければならないが、仮想の場合はその保証ができない。
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)

Implementation Defined: 関数がインライン化されるかどうかは処理系定義であるが、 関数リテラルは宣言スコープで使用される場合は インライン化されるべきである。

関数のオーバーロード

関数のオーバーロードは、同じスコープ内の2つ以上の関数に 同じ名前が指定された場合に発生する。 選択される関数は、引数に最も適合するものである。 適合レベルは以下の通りである。

  1. 一致しない
  2. 暗黙的変換による一致
  3. 修飾子変換による一致(引数の型が パラメータ型に修飾子変換可能である場合)
  4. 完全一致

名前付き引数は、 「引数とパラメータの一致」に従って候補が解決される。 これが失敗した場合(例えば、オーバーロードに名前付き引数と一致するパラメータがない場合など)、 一致レベルは一致しない。それ以外の場合、名前付き引数は一致レベルに影響しない。

各引数(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);  // エラー: あいまい
Rationale: this パラメータを必要としない静的メンバ関数は、 それを渡す必要はない。

オーバーロード セット

同じスコープで宣言された関数は、互いにオーバーロードされ、 オーバーロードセットと呼ばれる。 オーバーロードセットの例としては、モジュールレベルで定義された関数がある。

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

関数パラメータ

パラメータ ストレージクラス

パラメータの保存クラスは、inoutreflazyreturnscope である。 また、パラメータは、constimmutablesharedinout という型コンストラクタを取ることもできる。

inoutreflazy は相互に排他的である。最初の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 にバインドされ、acount に、berrno にバインドされる。

パラメータ ストレージ クラスと型 コンストラクタの概要
Storage ClassDescription
なしパラメータは、引数の"変更可能なコピー"となる。
inパラメータは関数への入力となる。
out引数は「l値」でなければならず、参照渡しされ、 関数呼び出し時にその型のデフォルト値(T.init )で初期化される。
refパラメータは入出力パラメータであり、参照渡しされる。
scope このパラメータは関数呼び出しから逃れてはならない (例えばグローバル変数に割り当てられるなど)。 参照型ではないパラメータは無視される。
returnパラメータは、最初のパラメータに返却またはコピーされる可能性があるが、 それ以外の場合には関数から抜け出さない。 このようなコピーは、派生元の引数を上書きしないようにする必要がある。 参照のないパラメータは無視される。 スコープパラメータを参照のこと。
lazy引数は呼び出された関数によって評価され、呼び出し元によって評価されるのではない
Type ConstructorDescription
const引数は暗黙的に const 型に変換される
immutable引数は暗黙的に不変型に変換される
shared引数は共有型に暗黙的に変換される
inout引数はinout型に暗黙的に変換される

パラメータ内

注釈: 以下の例では、-preview=in スイッチが必要であり v2.094.0 以降で使用可能である。 使用しない場合、inconst と同等である

パラメータは関数への入力である。入力パラメータは、const scope ストレージクラスを持つかのように動作する。 入力パラメータは、コンパイラによって参照渡しされる場合もある。

xml-ph-0000@deepl.internalパラメータとは異なり、xml-ph-0001@deepl.internalパラメータは左辺値と右辺値の両方にバインドできる (リテラルなど)。

ref パラメータとは異なり、in パラメータは、"l値"と"r値"の両方にバインドできる (リテラルなど)。

値として渡された場合に副作用が発生する型(コピーコンストラクタ、postblit、デストラクタを持つ型など)や、 コピーできない型 (例えば、コピーコンストラクタが@disable としてマークされている場合など)は、常に参照渡しされる。 動的配列、クラス、連想配列、関数ポインタ、およびデリゲートは 常に値渡しされる。

Implementation Defined: パラメータの型がこれらのカテゴリーのいずれにも該当しない場合、 参照渡しされるかどうかは処理系定義であり、バックエンドは プラットフォームの ABI に最も適合する方式を自由に選択できる。

参照および出力パラメータ

デフォルトでは、パラメータは"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
}
xml-ph-0000@deepl.internal変数のアドレスを返すことも、xml-ph-0001@deepl.internalコードでチェックされる。

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 refreturn scope の両方のセマンティクスを指定することはできない。 パラメータがref で渡され、returnscope の両方のストレージクラスを持つ場合、 return scopereturnscopeのキーワードが この順序で隣り合って表示されている場合にのみ、意味を持つ。return refscope パラメータを指定すると、スコープポインタへの参照を返すことができる。 それ以外の場合は、パラメータは 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 scopereturn refscope のセマンティクスの組み合わせの例:

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 指定できる可能性がある。 以下の条件を満たす必要がある。
その後、パラメータは関数の戻り値の型に応じて、scope またはreturn 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つの形式がある:

  1. C言語のスタイルの可変長引数関数
  2. 型情報付き可変長引数関数
  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-style
void 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リンケージであり、... を最後のパラメータとする。

... 唯一のパラメータとすることもできます。

... パラメータの前にパラメータがある場合は、... とそれらを区切るカンマが必要である。

注釈: カンマが省略された場合は、TypeSafe 可変長引数関数となる。
int abc(char c, ...);   // 必要なパラメータは1つ: c
int def(...);           // 必須パラメータはない
int ghi(int i ...);     // 型安全な可変長引数関数である
//int boo(, ...);       // エラー

2つの隠し引数が関数に渡される。

は、 可変個の引数の最初の引数への参照である。 可変個の引数にアクセスするには、 import

_argptr 可変長引数の最初の引数への参照である。 可変長引数にアクセスするには、 import core.vararg_argptrcore.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、コピーが作成されたので
}
Implementation Defined: 可変長オブジェクトまたは配列インスタンスは、 スタック上で構築される可能性がある。

他の型の場合は、引数は値として渡される。

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
}
Best Practices: デリゲートの配列パラメータを宣言する際には、 を使用する。 これにより、デリゲートに対してクロージャが生成されるのを防ぐことができる。 はデリゲートが関数から抜け出さないことを意味する。scope scope

隠しパラメータ

xml-ph-0000@deepl.internal

参照 スコープ リターン ケース

caseケース

定義

定義
TermDescription
I指示語を含まない
P間接を含む型
X間接参照を含む場合と含まない場合がある型
pP型のパラメータ
iパラメータの型I
参照ref または パラメータout
戻り値return 文によって返された
エスケープされグローバル変数またはその他のメモリに格納され、関数のスタックフレームには含まれない

分類

パラメータは以下の状態のいずれかである必要がある。

分類
TermDescription
なし 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 を意味する。

マッピング
ExampleClassificationComments
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; }

共分散

共分散とは、制約のあるパラメータを、より制約の少ないパラメータに変換できることを意味する。 これは、各状態の説明から推論できる。

注釈:refref 以外の値と共変ではないため、それらのエントリは 表から省略されている。
共分散
From\To None ReturnScopeScope
なし &#10004;
ReturnScope &#10004;&#10004;
スコープ &#10004;&#10004; &#10004;
参照 共分散
From\To Ref ReturnRefRefScopeReturnRef-ScopeRef-ReturnScope
参照 &#10004;&#10004;
ReturnRef &#10004;
RefScope &#10004;&#10004; &#10004;&#10004; &#10004;
ReturnRef-Scope &#10004; &#10004;
Ref-ReturnScope &#10004;&#10004; &#10004;

例えば、scope はすべての非参照パラメータに一致し、ref scope はすべての参照パラメータに一致する。

xml-ph-0000@deepl.internal

ローカル変数

ローカル変数は関数のスコープ内で宣言される。 関数パラメータが含まれる。

ローカル変数は、まず値を割り当てないと読み取ることができない。

Implementation Defined: 実装では、常にこれらのケースを検出できるとは限らない。

ローカルの非静的変数のアドレスまたは参照は、 関数から返すことができない。

同じ関数内のローカル変数とラベルに同じ名前を付けることはできない。

ローカル変数は、同じ関数内の別のローカル変数を隠すことはできない。

Rationale: これは、しばしば バグであるか、少なくともバグのように見える。 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

ローカル静的変数

staticshared 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
}

この制限に対するいくつかの回避策がある。

ネストされた関数はオーバーロードできない。

関数ポインタ、デリゲート、クロージャ

関数ポインタ

関数ポインタは、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);
}
Implementation Defined: 同一の本体を持つ2つの関数、または 同一のアセンブリコードにコンパイルされる2つの関数は、 異なる関数ポインタ値を持つことが保証されていない。実装では、 同一のコードにコンパイルされる場合、関数の本体を1つに統合することがある。
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ヒープ上に割り当てられる。

xml-ph-0000@deepl.internal
@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関数では許可されない。

Undefined Behavior: コンテキストポインタを必要とする関数を指すように設定された関数ポインタを呼び出す 。
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 を使用しており、これは デリゲートとして推論される。

最後の代入ではFunctionLiteral を使用しており、これは デリゲートとして推論される

注釈: 関数ポインタは、テンプレートを通して渡すことで、デリゲート引数を取る関数に渡すことができる。 std.functional.toDelegateテンプレートを通して渡すことで、呼び出し可能なものをすべてnull コンテキストポインタを持つデリゲートに変換する。
xml-ph-0000@deepl.internalxml-ph-0000@deepl.internal

匿名関数と匿名デリゲート

FunctionLiteralsを参照。

main() 関数

コンソールプログラムでは、main() がエントリーポイントとして機能する。 これは、すべてのモジュール初期化が実行された後、 およびすべてのユニットテストが実行された後に呼び出される。 これが返された後、すべてのモジュールデストラクタが実行される。main() は、以下のように宣言しなければならない。

        MainFunction:
            MainReturnDecl main() MainFunctionBody
            MainReturnDecl main(string[] Identifier) MainFunctionBody
MainReturnDecl: void int noreturn auto
MainFunctionBody: ShortenedFunctionBody SpecifiedFunctionBody

string[] パラメータが宣言されている場合、そのパラメータには OSからプログラムに渡された引数が格納される。最初の引数は通常、 実行可能ファイル名であり、それにコマンドライン引数が続く。

注釈: 実行時環境は、接頭辞--DRT- の引数をすべて削除できる。
注釈:前述の戻り値/パラメータ型には、constimmutable で注釈を付けることができる。また、enum で、一致する基本型に置き換えることもできる。

主関数はDリンケージでなければならない。

必要に応じて属性を追加できる。例えば、@safe@nogcnothrow など。

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 と呼ばれ、現在の環境変数に関する情報を保持する。

注釈: ストレージクラス/enum のDmain 関数用に定義されたものは、 Cmain 関数にも適用される。

この関数は C の main 関数の代わりとなり、 Dmain 関数に関連するセットアップやテアダウンなしですぐに実行される。モジュール コンストラクタ、モジュールデストラクタ、または unittests に依存するプログラムでは、 適切なランタイム関数を使用して (デ) 初期化を手動で実行する必要がある。

Implementation Defined: Windowsシステムでは、"@system"や"@email"など、その他のシステム固有のエントリーポイントが存在する可能性がある。 や など。 WinMain DllMain
注釈:main に対して異なる署名を必要とするプラットフォームをターゲットとするプログラムは、 明示的なマングリング機能を使用できる。
pragma(mangle, "main")
int myMain(int a, int b, int c)
{
    return 0;
}

関数テンプレート

関数には、テンプレート形式のコンパイル時の引数を指定できる。 関数テンプレートを参照。

functionfunction

コンパイル時関数実行(CTFE)

コンパイル時の値が必要なコンテキストでは、 関数を使用してこれらの値を計算することができる。これをコンパイル時関数実行 (CTFE)と呼ぶ。

これらのコンテキストは:

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には以下の制限がある。

  1. 式はグローバルまたはローカルの静的変数を参照できない。
  2. アセンブリステートメントは許可されていない
  3. 非ポータブルキャスト(例えば、int[] からfloat[] へのキャストなど)は許可されない。 エンディアンに依存するキャストも許可されない。 符号付き型と符号なし型の間のキャストは許可される。
  4. 共用体で重複するフィールドの再解釈は許可されない。

CTFEでは、安全に使用される限り、ポインタは許可される。

上記の制限は、実際に実行される式のみに適用される。 例えば:

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 は、 CTFEで禁止されている操作を回避するための代替の実行パスを提供するために使用できる 。__ctfe の使用はすべて静的に評価され 、実行時にコストは発生しない。

回復不能なエラー(アサートエラーなど)は不正である。

Implementation Defined: CTFE経由で関数を実行すると、 実行時に実行するよりもかなり時間がかかる。 関数が無限ループに入ると、コンパイラがハングアップする可能性がある。
Implementation Defined: CTFE 経由で実行される関数は、 処理系定義または未定義の動作が発生した場合、実行時とは異なる結果を 生じる可能性がある。 "Undefined-behavior未定義の動作
to hang実行する

文字列ミックスインとコンパイル時の関数実行

CTFEで実行されるすべての関数は、 実行時にも実行可能でなければならない。関数のコンパイル時評価は、 実行時にその関数を実行することと等価である。関数のセマンティクスは、 その関数のコンパイル時値に依存することはできない。例えば:

int foo(string s)
{
    return mixin(s);
}

const int x = foo("1");
は無効である。なぜなら、foo の実行時コードを生成できないからだ 。
Best Practices: s がテンプレート引数である関数テンプレートは、 この種のものを実装する適切な 方法であろう。
internalinternal

No-GC Functions

No-GC関数は、@nogc 属性でマークされた関数である。 これらの関数は、GCヒープ上にメモリを割り当てない。 No-GC関数では、以下の操作は許可されていない。

  1. ヒープ上に配列を構築する
  2. .length プロパティ配列への書き込みによる配列のサイズ変更
  3. 配列の連結
  4. 配列の追加
  5. 連想配列の作成
  6. 連想配列の添字付け
    注釈: 指定したキーが存在しない場合はRangeError がスローされる可能性があるため、
  7. ヒープ上に new でオブジェクトを割り当てる
    注釈: 関数スコープ内でのclass typesnew 宣言は、scope 変数に使用される場合、スタック上に割り当てが行われるため、@nogc と互換性がある
  8. @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 &amp; 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
}
Best Practices: @nogc とマークされた関数はGCによる割り当てを行わないため、 GCコレクションが実行されないことを意味します。しかし、 別のスレッドがGCで割り当てを行い、コレクションが実行される可能性はあります。 GCコレクションの実行を防ぐ推奨される方法は、 core.memory.GC.disable() を代わりに呼び出すことです。これにより、 への対応する呼び出しが実行されるまで、どのスレッドでもコレクションが実行されなくなります。 が有効な場合、GC割り当ては実行されます core.memory.GC.enable() GC.disable()

関数安全

安全関数

安全関数は、@safe 属性でマークされている。@safe は推論できる。 関数属性の推論を参照。

安全な関数には安全なインターフェイスがある。 実装では、 関数の本体を安全であることが分かっている操作に制限することで、@trusted 関数への呼び出しを除いて、これを強制しなければならない。

以下の制限は、コンパイラによって安全な関数で強制される。

注釈: 配列のインデックスまたはスライスを行う際、範囲外のアクセスは 実行時エラーを引き起こす。

安全な関数の中にネストされた関数は、デフォルトで安全な関数となる。

安全な関数は、信頼された関数やシステム関数と共変する。

Best Practices: 可能な限り多くの関数を としてマークする。@safe

安全な外部関数

外部関数には、コンパイラから見える関数本体がない。

@safe extern (C) void play();
そのため、安全性を自動的に検証することはできません。
Best Practices: 外部関数に対しては、デフォルト設定に頼らず、明示的に属性を設定する。

信頼できる関数

信頼された関数は、@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は安全でないエイリアシングを導入できない。
}

信頼された関数は、安全な関数、信頼された関数、またはシステム関数を呼び出すことができる。

信頼された関数は、安全な関数またはシステム関数と共変である。

Best Practices: 信頼された関数は、手動で検証しやすくするために、小さく保つべきである。

システム関数

システム関数は、@safe または@trustedのマークが付いておらず、 @safe 関数の内部にネストされていない関数である。 システム関数は、@system 属性が付いている場合がある。 システム関数であるということは、その関数が実際に安全でないことを意味するのではなく、 その安全性を手動で検証する必要があることを意味する。

システム関数は、信頼された関数や安全な関数と共変しない

システム関数は、安全関数および信頼済み関数を呼び出すことができる。

Best Practices: 疑問がある場合は、 および 関数を としてマークする。D コンパイラでは、それらの実装が D ではない場合、 チェックできないためである。それらのほとんどは であるが、手動で チェックする必要がある。extern (C) extern (C++) @system @safe
Best Practices: システム関数の数とサイズは最小限にすべきである。 これにより、手動で安全性を確認するために必要な作業を最小限に抑えることができる。

安全なインターフェース

関数呼び出しの引数、コンテキスト、 アクセス可能なグローバル変数のそれぞれが安全な値を持ち、 安全なエイリアシングが適用されている場合、 その関数は安全なインターフェースを持つ。

未定義の動作を示すことが できず、 かつ
  1. 未定義の動作を示すことがなく 、 かつ
  2. @safe のコードからアクセス可能な安全でない値を生成することができない (例えば、戻り値、グローバル変数、ref パラメータを介してなど)、
  3. @safe コードからアクセス可能な安全でないエイリアシングを導入することはできない。

これらの要件を満たす関数は、 @safeまたは @trusted。これらの要件を満たさない関数は、 @system

例:

安全な値

bool では、0と1のみが安全な値である。

その他のすべての基本データ型については、 すべての可能なビットパターンが安全である。

ポインタは、以下のいずれかの場合は安全な値である。

  1. null
  2. 有効なメモリオブジェクトを指しており、 そのメモリオブジェクト内の指し示された値が安全である場合、
例:

例:

xml-ph-0000@deepl.internal
int* 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は安全である。 */

動的配列は、以下の条件を満たす場合に安全である。

  1. ポインタが安全であり、
  2. その長さが対応するメモリオブジェクトの範囲内であり、 そして
  3. すべての要素が安全である。

例:

xml-ph-0000@deepl.internal
int[] 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は安全であったが、関数が戻ると
        ポインタが無効になるため、返される動的配列は
        安全でない。 */
}

静的配列は、すべての要素が安全である場合に安全である。 要素の型に関係なく、長さがゼロの静的配列は常に安全である。

連想配列は、そのキーと要素がすべて安全である場合に安全である。

構造体/共用体のインスタンスは、以下の条件を満たす場合に安全である。

  1. アクセス可能なフィールドの値が安全であり、
  2. 共用体を使用して安全でないエイリアスを導入しない。

例:

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 または: の場合、安全である。

  1. そのクラス型の有効なクラスインスタンス、または そのクラス型から派生した型を参照しており、
  2. インスタンスのアクセス可能なフィールドの値が安全であり、
  3. 共用体との間で安全でないエイリアシングを導入しない。

関数ポインタは、それがnull であるか、または 同じか上位互換のシグネチャを持つ有効な関数を参照している場合は安全である。

delegate が安全であるのは、以下の場合である。

そのxml-ph-0000@deepl.internalプロパティがxml-ph-0001@deepl.internalであるか、 またはデリゲート型と一致するか、またはそれと共変する関数を参照している
  1. その.funcptr プロパティがnull であるか、または デリゲート型と一致するか、またはそれと共変する関数を参照しており、
  2. その.ptr プロパティがnull であるか、または関数で期待される形式のメモリオブジェクトを参照している 。

安全なエイリアス

1つのメモリロケーションが2つの異なる型でアクセス可能な場合、 そのエイリアスは安全であるとみなされる。

両方のタイプがxml-ph-0000@deepl.internalまたはxml-ph-0001@deepl.internalである場合、または、
  1. 両方の型がconst またはimmutable である場合、または
  2. 一方の型が変更可能で、もう一方がconst 修飾された 基本データ型である場合、または
  3. 両方の型が変更可能な基本データ型である場合、または
  4. 型のいずれかが長さゼロの静的配列型である場合、または
  5. いずれかの型が非ゼロ長の静的配列型であり、 配列の要素型と他の型のエイリアシングが安全である場合、または
  6. 両方の型がポインタ型であり、対象の型のエイリアシングが 安全であり、対象の型が同じサイズである。

その他のすべてのケースのエイリアシングは、安全ではないとみなされる。

注釈: 安全なエイリアシングは、安全なインターフェースを持つ関数に公開される場合があるが、 その関数の安全性を損なうことはない。 安全でないエイリアシングは安全性を保証しない。
注釈: 安全なエイリアシングは、データに対するエイリアス化されたビューのすべてが安全な値を持つことを意味するものではない。 それらの安全性は個別に確認する必要がある。

例:

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 と推論される。

関数がそれらの属性について自身をテストしようとする場合、 その関数はそれらの属性を持たないと推測される。

Rationale: 関数属性の推論により、ユーザーが関数に属性を追加する必要性が大幅に削減される。 特にテンプレート化された関数において。 xml-ph-0000@deepl.internal

Uniform Function Call Syntax (UFCS)

無料の関数は、以下の両方の条件を満たす場合、メンバ関数のように呼び出すことができる。

オブジェクト式に対してメンバ関数が存在しない(または存在できない) オブジェクト式は任意の型でよい。 これはUFCS関数呼び出しと呼ばれる。

オブジェクト式は任意の型でよい。 これはUFCS関数呼び出しと呼ばれる。

void sun(T, int);

void moon(T t)
{
    t.sun(1);
    // `T`にメンバ関数`sun`がない場合、
    // `t.sun(1)`は`sun(t, 1)`と書かれたものとして解釈される。
}
Rationale: これにより、外部関数をあたかもそれが パブリックな final。 これにより、クラスの関数の数を必要最小限の ものだけに抑えることができる。オブジェクトのプライベート状態を管理するために必要なものだけであり、 ありとあらゆる関数を追加したいという誘惑にかられることなく、 また、 関数チェーンやコンポーネントプログラミングも可能になる。

より複雑な例:

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関数の検索は通常通り行われる。

Rationale: 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[])を使用している
    }
}