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

宣言

文法

宣言は 、DeclDefに含まれるため、関数本体の内部でDeclarationStatementとして使用できるだけでなく、 関数の外部でも使用できる。

Declaration:
    FuncDeclaration
    VarDeclarations
    AliasDeclaration
    AliasAssign
    AggregateDeclaration
    EnumDeclaration
    ImportDeclaration
    ConditionalDeclaration
    StaticForeachDeclaration
    StaticAssert
    TemplateDeclaration
    TemplateMixinDeclaration
    TemplateMixin

集約

AggregateDeclaration:
    ClassDeclaration
    InterfaceDeclaration
    StructDeclaration
    UnionDeclaration

変数宣言

VarDeclarations:
    StorageClassesopt BasicType TypeSuffixesopt IdentifierInitializers ;
    AutoDeclaration
IdentifierInitializers: IdentifierInitializer IdentifierInitializer , IdentifierInitializers
IdentifierInitializer: Identifier Identifier TemplateParametersopt = Initializer
Declarator: TypeSuffixesopt Identifier

参照:

ストレージクラス

「型クラスとストレージクラス」を参照。

StorageClasses:
    StorageClass
    StorageClass StorageClasses
StorageClass: LinkageAttribute AlignAttribute AtAttribute deprecated enum static extern abstract final override synchronized auto scope const immutable inout shared __gshared Property nothrow pure ref

宣言構文

宣言構文は通常、配列も含めて右から左に読む。

int x;    // xはintである
int* x;   // xはintへのポインタである
int** x;  // xはintへのポインタへのポインタである

int[] x;  // xはintの配列である
int*[] x; // xはintへのポインタの配列である
int[]* x; // xはintの配列へのポインタである

int[3] x;     // xは3 intの静的配列である
int[3][5] x;  // xは3つのintからなる5つの静的配列
int[3]*[5] x; // xは3 intの静的配列へのポインタ5個からなる静的配列

ポインタ配列 およびTypeSuffixを参照。

関数ポインタ

関数ポインタはfunction キーワードを使用して宣言される。

int function(char) x; // xはcharを引数にとり
                     // intを返す関数
                     // へのポインタである
int function(char)[] x; // xはcharを引数にとり
                     // intを返す関数
                     // へのポインタの配列である
                     // 

C言語のスタイルでの宣言

C言語のスタイルによる配列、関数ポインタ、および配列へのポインタの宣言は サポートされていない。以下のC言語の宣言は比較のみを目的としている。

int x[3];          // C 3つのintの静的配列
int x[3][5];       // C 5つのintの配列3つからなる静的配列
int (*x[5])[3]; // C 3つのintの静的配列への5つのポインタの静的配列 int (*x)(char); // C char引数を受け取り、intを // 返す関数へのポインタ int (*[] x)(char); // C char引数を取り、intを // 返す関数へのポインタの配列
Rationale:
  • D型では右から左に読み進めるだけでよいが、 Cでは括弧が必要な場合があり、 時計回り/らせん規則を使用して反復的に型を読み進める必要がある。
  • C関数ポインタ宣言の場合、a (*b)(c); 、Cパーサーは それを明確に解析するために型検索を試みる必要がある。 a という関数への呼び出しであり、関数ポインタを返し、 すぐに呼び出される可能性がある。D関数ポインタ構文は 明確であり、型を前方宣言する必要がない。

複数のシンボルの宣言

複数のシンボルを宣言する宣言では、すべての宣言は 同じ型でなければならない。

int x, y;   // xとyはintである
int* x, y;  // xとyはintへのポインタである
int[] x, y; // xとyはintの配列である

これはC言語とは対照的である。

int x, *y;  // xはint、yはintへのポインタである
int x[], y; // xは配列/ポインタ、yはintである

初期化

Initializer:
    VoidInitializer
    NonVoidInitializer
NonVoidInitializer: AssignExpression ArrayLiteral StructInitializer

初期化子が指定されない場合、変数は その型のデフォルト.initに 設定される。

変数は、NonVoidInitializerで初期化することができる。

参照:配列の初期化

無効な初期化

VoidInitializer:
    void

通常、変数は初期化される。 しかし、変数の初期化子がvoid の場合、変数は初期化されない安全でない値を含む可能性がある型(ポインタ型など)の変数に対するvoid初期化子は、@safe コードでは許可されない。

Implementation Defined: void初期化変数の値が 設定される前に使用された場合、その値は処理系定義となる。
void bad()
{
    int x = void;
    writeln(x);  // 実装定義値を表示する
}
voidで初期化された変数の値が 設定される前に使用され、その値が参照、ポインタ、または不変の構造体のインスタンスである場合、 動作は未定義である。 xml-ph-00
Undefined Behavior: void初期化変数の値が 設定される前に使用され、その値が参照、ポインタ、または不変の構造体のインスタンスである場合、動作は未定義である。
void muchWorse()
{
    char[] p = void;
    writeln(p);  // 黙示録が起こるかもしれない
}
Best Practices:
  1. スタティック配列がスタック上にある場合、Void初期化子は有用であるが、 一時バッファのように部分的にしか使用できない場合もある。 Void初期化子はコードの高速化につながる可能性があるが、配列要素が読み込まれる前に必ず設定されるようにしなければならないため、リスクも伴う 。
  2. これは構造体についても同様である。
  3. 個々のローカル変数に対して void 初期化子を使用することはほとんど役に立たない。 最新の最適化機能では、後で初期化される場合は、初期化の不要な保存領域が削除されるからだ。
  4. ホットコードパスについては、void初期化子が実際に結果を改善するかどうかを確認するために、プロファイリングを行う価値がある。

暗黙の型推論

AutoDeclaration:
    StorageClasses AutoAssignments ;
AutoAssignments: AutoAssignment AutoAssignments , AutoAssignment
AutoAssignment: Identifier TemplateParametersopt = Initializer

宣言がStorageClassで始まり、 型が推論可能なNonVoidInitializerを持つ場合、 宣言の型は省略できる。

static x = 3;      // xはint型である
auto y = 4u;       // yはuint型である

auto s = "Apollo"; // sはimmutable(char)[]型、すなわち文字列である

class C { ... }

auto c = new C();  // cはクラスCのインスタンスへのハンドルである

NonVoidInitializerには前方参照を含めることはできない (将来的にこの制限が撤廃される可能性もある)。 暗黙的に推論された型は、 実行時ではなくコンパイル時に宣言に静的にバインドされる。

ArrayLiteralは、 静的配列ではなく動的配列の型と推論される。

auto v = ["resistance", "is", "useless"]; // 型はstring[]であり、string[3]ではない

グローバルおよび静的初期化子

グローバル変数または静的変数の初期化子は、 コンパイル時に評価可能でなければならない。 実行時の初期化は静的コンストラクタによって行われる。

Implementation Defined:
  1. いくつかのポインタが他の関数やデータのアドレスで初期化できるかどうか 。

エイリアス宣言

注釈: 新しいコードでは、AliasAssignments形式のみを使用すべきである。
AliasDeclaration:
    alias StorageClassesopt BasicType TypeSuffixesopt Identifiers ;
    alias StorageClassesopt BasicType FuncDeclarator ;
    alias AliasAssignments ;
Identifiers: Identifier Identifier , Identifiers
AliasAssignments: AliasAssignment AliasAssignments , AliasAssignment
AliasAssignment: Identifier TemplateParametersopt = StorageClassesopt Type Identifier TemplateParametersopt = FunctionLiteral Identifier TemplateParametersopt = StorageClassesopt Type Parameters MemberFunctionAttributesopt

エイリアス宣言は、型または別のシンボルを参照するシンボル名を作成する。 その名前は、対象が現れる可能性がある場所であればどこでも使用できる。 エイリアス化できるものは以下のとおりである。

型エイリアス

alias myint = abc.Foo.bar;

エイリアス型は、エイリアスされた型と意味的には同一である。 デバッガーは両者を区別することができず、関数オーバーロードに関しては違いはない。 例えば:

alias myint = int;

void foo(int x) { ... }
void foo(myint m) { ... } // エラー、多重定義関数fooである

型エイリアスは、他のシンボルエイリアスと区別がつかないように見えることがある。

alias abc = foo.bar; // 型なのかシンボルなのか?
Best Practices: 単純な基本型名をエイリアス化する場合を除き、 型エイリアス名は大文字で表記すべきである。

シンボルエイリアス

シンボルは、別のシンボルのエイリアスとして宣言することができる。 例:

import planets;

alias myAlbedo = planets.albedo;
...
int len = myAlbedo("Saturn"); // 実際にはplanets.albedo()を呼び出している

以下のエイリアス宣言は有効である。

template Foo2(T) { alias t = T; }
alias t1 = Foo2!(int);
alias t2 = Foo2!(int).t;
alias t3 = t1.t;
alias t4 = t2;

t1.t v1;  // v1はint型である
t2 v2;    // v2はint型である
t3 v3;    // v3はint型である
t4 v4;    // v4はint型である

エイリアス付きのシンボルは、長い修飾シンボル名の省略形として、あるいは あるシンボルへの参照を別のシンボルにリダイレクトする方法として有用である 。

version (Win32)
{
    alias myfoo = win32.foo;
}
version (linux)
{
    alias myfoo = linux.bar;
}

エイリアスは、 インポートされたモジュールまたはパッケージからシンボルを「インポート」するために使用できる。 現在のスコープにインポートする:

static import string;
...
alias strlen = string.strlen;

オーバーロードセットのエイリアス

エイリアスは、現在のスコープで関数でオーバーロードされているオーバーロード関数のセットを「インポート」することもできる。

class B
{
    int foo(int a, uint b) { return 2; }
}

class C : B
{
    // オーバーロードを宣言すると、ベースクラスのオーバーロードが隠蔽される
    int foo(int a) { return 3; }
    // 隠されたオーバーロードを再宣言する
    alias foo = B.foo;
}

void main()
{
    import std.stdio;

    C c = new C();
    c.foo(1, 2u).writeln;   // B.fooを呼び出す
    c.foo(1).writeln;       // C.fooを呼び出す
}

変数のエイリアス

変数はエイリアス化できるが、"式"はできない。

int i = 0;
alias a = i; // OK
alias b = a; // alias a variable alias
a++;
b++;
assert(i == 2);

//alias c = i * 2; // エラー
//alias d = i + i; // エラー

集約のメンバはエイリアス化できるが、非静的 フィールドのエイリアスは親の型外からはアクセスできない。

struct S
{
    static int i = 0;
    int j;
    alias a = j; // OK

    void inc() { a++; }
}

alias a = S.i; // OK
a++;
assert(S.i == 1);

alias b = S.j; // 許可
static assert(b.offsetof == 0);
//b++;   // エラー、Sのインスタンスがない
//S.a++; // エラー、Sのインスタンスがない

S s = S(5);
s.inc();
assert(s.j == 6);
//alias c = s.j; // 廃止予定

関数型のエイリアス

関数型にはエイリアスを付けることができる。

alias Fun = int(string);
int fun(string) {return 0;}
static assert(is(typeof(fun) == Fun));

alias MemberFun1 = int() const;
alias MemberFun2 = const int();
// 先頭の属性は戻り値型ではなくfuncに適用される
static assert(is(MemberFun1 == MemberFun2));

型エイリアスを使用して、異なるデフォルト引数を持つ関数を呼び出したり、 必須引数をデフォルト引数に変更したり、その逆に変更したりすることができる。

import std.stdio : writeln;

void fun(int v = 6)
{
    writeln("v: ", v);
}

void main()
{
    fun();  // v: 6を表示する

    alias Foo = void function(int=7);
    Foo foo = &fun;
    foo();  // v: 7を表示する
    foo(8); // v: 8を表示する
}
import std.stdio : writefln;

void main()
{
    fun(4);          // a: 4, b: 6, c: 7を表示する

    Bar bar = &fun;
    //bar(4);           // コンパイルエラー、`Bar`エイリアスは
                        // 明示的な第2引数を必要とするため
    bar(4, 5);          // a: 4, b: 5, c: 9を表示する
    bar(4, 5, 6);       // a: 4, b: 5, c: 6を表示する

    Baz baz = &fun;
    baz();              // a: 2, b: 3, c: 4を表示する
}

alias Bar = void function(int, int, int=9);
alias Baz = void function(int=2, int=3, int=4);

void fun(int a, int b = 6, int c = 7)
{
    writefln("a: %d, b: %d, c: %d", a, b, c);
}

エイリアス割り当て

AliasAssign:
    Identifier = Type

エイリアス宣言(AliasDeclaration)にはエイリアス割り当て(AliasAssign)で新しい値を割り当てることができる。

template Gorgon(T)
{
    alias A = long;
    A = T; // Aに新しい値を代入する
    alias Gorgon = A;
}
pragma(msg, Gorgon!int); // intを表示する
Best Practices: AliasAssignは、再帰的な計算ではなく反復的な計算を使用する場合に特に有用である。 なぜなら、再帰的な計算で生成される 多数の中間テンプレートを作成する必要がなくなるからだ。
import std.meta : AliasSeq;

static if (0) // 比較のための再帰メソッド
{
    template Reverse(T...)
    {
        static if (T.length == 0)
            alias Reverse = AliasSeq!();
        else
            alias Reverse = AliasSeq!(Reverse!(T[1 .. T.length]), T[0]);
    }
}
else // テンプレートのインスタンス化を最小化する反復メソッド
{
    template Reverse(T...)
    {
        alias A = AliasSeq!();
        static foreach (t; T)
            A = AliasSeq!(t, A); // 別名 Assign
        alias Reverse = A;
    }
}

enum X = 3;
alias TK = Reverse!(int, const uint, X);
pragma(msg, TK); // tuple(3, (const(uint)), (int))を表示する

エイリアスの再割り当て

AliasReassignment:
    Identifier = StorageClassesopt Type
    Identifier = FunctionLiteral
    Identifier = StorageClassesopt BasicType Parameters MemberFunctionAttributesopt

テンプレート内のエイリアス宣言は、新しい値に再代入することができる。

import std.meta : AliasSeq;

template staticMap(alias F, Args...)
{
    alias A = AliasSeq!();
    static foreach (Arg; Args)
        A = AliasSeq!(A, F!Arg); // 別名の再割り当て
    alias staticMap = A;
}

enum size(T) = T.sizeof;
static assert(staticMap!(size, char, wchar, dchar) == AliasSeq!(1, 2, 4));

識別子は()()的に先行するAliasDeclaration に解決されなければならない。 両者は同じ TemplateDeclaration のメンバーでなければならない。

AliasReassignmentの右辺は、AliasDeclarationの右辺を置き換える。

AliasDeclaration が AliasReassignment の右辺以外で参照された場合、 AliasDeclarationは再代入できなくなる。

Rationale: エイリアスの再割り当ては、コンパイル時間の短縮やメモリ消費量の削減につながる可能性がある。 また、再帰的な代替方法よりもはるかにシンプルなコードで実現できる。

外部宣言

extern の記憶クラスを持つ変数宣言は、 モジュール内に記憶領域を割り当てない。これらは、他のオブジェクトファイルで定義する必要があり、 一致する名前をリンクする必要がある。

extern 宣言には、オプションでexternのリンク属性を続けることができる。 リンク属性がない場合は、 デフォルトでextern(D) となる。

// このモジュール内でCで割り当てられ、初期化された変数
extern(C) int foo;
// Cリンケージでこのモジュール外に割り当てられた変数
// (静的にリンクされたCライブラリや別のモジュールなど)
extern extern(C) int bar;
Best Practices:
  1. 外部宣言の主な利点は、 CまたはC++ファイルのグローバル変数宣言および関数と接続することである。

型修飾子とストレージクラス

型修飾子ストレージクラスは、それぞれ異なる概念である。

型修飾子は、既存の基本型から派生型を作成するもので あり、作成された型は、その型の複数のインスタンスを作成するために使用できる。

例えば、immutable という型修飾子は、 "不変の型"の変数を作成するために使用できる。

immutable(int)   x; // typeof(x) == immutable(int)
immutable(int)[] y; // typeof(y) == immutable(int)[]
                    // typeof(y[0]) == immutable(int)

// 型コンストラクタはエイリアスが可能な新しい型を生成する:
alias ImmutableInt = immutable(int);
ImmutableInt z;     // typeof(z) == immutable(int)

一方、ストレージクラスは新しい型を作成するのではなく、 変数や宣言中の関数で使用されるストレージの種類のみを記述する。 例えば、メンバ関数はconst ストレージクラスで宣言することで、暗黙的なthis 引数を変更しないことを示すことができる

struct S
{
    int x;
    int method() const
    {
        //x++;    // エラー: このメソッドはconstであり、this.xを変更できない
        return x; // OK: まだthis.xを読むことができる。
    }
}

いくつかのキーワード 型修飾子ストレージクラスの両方として使用できるが、ref のように、新しい型の構築に使用できないストレージクラスもある。

ref ストレージクラス

ref で宣言されたパラメータは 参照渡しされる。

void func(ref int i)
{
    i++; // iの変更は呼び出し側で見ることができる。
}

void main()
{
    auto x = 1;
    func(x);
    assert(x == 2);

    // しかし、refは型修飾子ではないので、以下は不正である:
    //ref(int) y; // エラー: refは型修飾子ではない。
}

関数ref として宣言することができ、 その場合は戻り値が参照渡しされることを意味する。

ref int func2()
{
    static int y = 0;
    return y;
}

void main()
{
    func2() = 2; // func2()の返却値は変更できる。
    assert(func2() == 2);

    // しかし、func2()によって返された参照は、変数に伝搬しない。
    // なぜなら、'ref'は返却値自体にのみ適用され、
    // それ以降に生成される変数には適用されないからである:
    auto x = func2();
    static assert(is(typeof(x) == int)); // N.B.: *not* ref(int);
                                     // ref(int)という型は存在しない。
    x++;
    assert(x == 3);
    assert(func2() == 2); // xxは、func2()が返したものへの参照ではない;
                          // func2()からrefストレージクラスを継承していない。
}

修飾された型を返すメソッド

const などのキーワードは、 型修飾子としても、またストレージクラスとしても使用できる。 この区別は、キーワードが使用される構文によって決定される。

struct S
{
    /* ここでのconstは型修飾子なのか、それとも格納クラスなのか?
     * 戻り値はconst(int)なのか、それとも
     * (変更可能な)intを返すconst関数なのか?
     */
    const int* func() // const関数である。
    {
        //++p;          // エラー、this.pはconstである
        //return p;     // エラー、const(int)*をint*に変換できない
        return null;
    }

    const(int)* func() // const intへのポインタを返す関数
    {
        ++p;          // OK、this.pは変更可能
        return p;     // OK、int*は暗黙のうちにconst(int)*に変換できる
    }

    int* p;
}
Best Practices: 混乱を避けるため、 戻り値の型には括弧付きの型修飾子の構文を使用し、 関数の記憶クラスは宣言の左側ではなく右側に記述するべきである。
struct S
{
    // ここで'const'が返却型に適用されることは明らかである:
    const(int) func1() { return 1; }

    // そして、ここでの'const'が関数に適用されることは明らかである:
    int func2() const { return 1; }
}