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

演算子オーバーロード

演算子のオーバーロードは、演算子がクラスや構造体のオブジェクトである演算子を、特別な名前の演算子の呼び出しに書き換えることで達成される。 演算子のオーバーロードは、クラスまたは構造体オブジェクトをオペランドとする演算子を、特別に命名されたメンバへの呼び出しに書き換えることによって達成される。 メンバの呼び出しに書き換えることで達成される。追加の構文は使用しない。

単項演算子オーバーロード

オーバーロード可能な単項演算子
oprewrite
-e e.opUnary!("-")()
+e e.opUnary!("+")()
~e e.opUnary!("~")()
*e e.opUnary!("*")()
++e e.opUnary!("++")()
--e e.opUnary!("--")()

例えば、構造体Sに対して- (否定)演算子をオーバーロードするには、次のようにする。 をオーバーロードする:

struct S
{
    int m;

    int opUnary(string s)() if (s == "-")
    {
        return -m;
    }
}

void main()
{
    S s = {2};
    assert(-s == -2);
}
注釈:上記のopUnaryは、テンプレート・パラメーター特化を使って宣言することもできる:
    int opUnary(string s : "-")()

ポストインクリメントe++ およびポストデクリメントe-- 演算子

これらは直接オーバーロードすることはできない。 書き換える:

接頭辞演算子の書き換え
oprewrite
e-- (auto t = e, e.opUnary!"--", t)
e++ (auto t = e, e.opUnary!"++", t)

インデックス単項演算子のオーバーロード

インデックスはオーバーロードできる。 インデックス式の単項演算子は、独立してオーバーロードすることもできる。 これは多次元インデックスに有効である。

オーバーロード可能なインデックス単項演算子
oprewrite
-a[b1,b2, ...bn] a.opIndexUnary!("-")(b1,b2, ...bn)
+a[b1,b2, ...bn] a.opIndexUnary!("+")(b1,b2, ...bn)
~a[b1,b2, ...bn] a.opIndexUnary!("~")(b1,b2, ...bn)
*a[b1,b2, ...bn] a.opIndexUnary!("*")(b1,b2, ...bn)
++a[b1,b2, ...bn] a.opIndexUnary!("++")(b1,b2, ...bn)
--a[b1,b2, ...bn] a.opIndexUnary!("--")(b1,b2, ...bn)
struct S
{
    private int[] a;

    void opIndexUnary(string s: "++")(size_t i) { ++a[i]; }
}

S s = {[4]};
++s[0];
assert(s.a[0] == 5);

スライス単項演算子のオーバーロード

スライスはオーバーロードできる。 スライスの単項演算子は独立してオーバーロードすることもできる。 opIndexUnary は、完全なスライスに対しては関数引数なしで定義される、 または、スライスの開始インデックスと終了インデックスの2つの引数で定義される。

オーバーロード可能なスライス単項演算子
oprewrite
-a[ i..j] a.opIndexUnary!("-")( a.opSlice( i,j))
+a[ i..j] a.opIndexUnary!("+")( a.opSlice( i,j))
~a[ i..j] a.opIndexUnary!("~")( a.opSlice( i,j))
*a[ i..j] a.opIndexUnary!("*")( a.opSlice( i,j))
++a[ i..j] a.opIndexUnary!("++")( a.opSlice( i,j))
--a[ i..j] a.opIndexUnary!("--")( a.opSlice( i,j))
-a[ ] a.opIndexUnary!("-")()
+a[ ] a.opIndexUnary!("+")()
~a[ ] a.opIndexUnary!("~")()
*a[ ] a.opIndexUnary!("*")()
++a[ ] a.opIndexUnary!("++")()
--a[ ] a.opIndexUnary!("--")()
struct S
{
    private int[] a;

    void opIndexUnary(string s: "--")() { --a[]; }
}

S s = {[1, 2]};
--s[];
assert(s.a == [0, 1]);
注釈:後方互換性のため、上記の書き換えがコンパイルに失敗し、かつ opSliceUnary が定義されている場合は a.opSliceUnary!(op)(i, j)a.opSliceUnary!(op) がそれぞれ代わりに試される。

キャスト演算子のオーバーロード

ある型を別の型にキャストする方法を定義するには、以下のように opCast テンプレート・メソッドを定義する:

キャスト演算子
oprewrite
cast() e e.opCast!()()

opCast 、明示的な式でのみ使用されることに注意されたい。cast ただし、ブーリアン演算の場合を除く(次項参照)。 節を参照)を除いては、明示的な式でのみ使われることに注意しよう。

struct S
{
    void* mem;

    bool opCast(T)()
    if (is(T == bool)) => mem !is null;
}

S s = S(new int);
auto b = cast(bool) s;
assert(b);
//b = s; // エラー

opCast の戻り値の型が、 のパラメー タと異なる場合、結果は暗黙のうちに型変換される。 cast の型パラメタと異なる場合、結果は暗黙のうちに変換される。

ブール演算

オーバーロードされた単項演算子のリストにないのは、論理否定演算子である。! 論理否定演算子である。もっとわかりにくいのは、 の結果に変換する単項演算子 bool もない。 その代わり、構造体に関しては、これらの演算子は "への書き換え"でカバーされている:

opCast!(bool)(e)

だからだ、

if (e)   =>  if (e.opCast!(bool))
if (!e)  =>  if (!e.opCast!(bool))

他のブーリアン条件式や論理演算子も同様である。 論理演算子も同様である。 も同様である。

ただし、これは のインスタンスに対してのみ起こる。クラス参照は、クラス参照がNULLかどうかをチェックすることでbool に変換される。 に変換される。

二項演算子オーバーロード

以下の二項演算子はオーバーロード可能である:

オーバーロード可能な二項演算子
+-*/%^^&
|^<<>>>>>~in

式である:

a op b

のいずれかに書き換えられる:

a.opBinary!("op")(b)
b.opBinaryRight!("op")(a)

となり、「より良く」マッチする方が選択される。 両方が同じようにマッチするのはエラーである。例:

struct S
{
    int[] data;

    // this ~ rhs
    int[] opBinary(string op : "~")(int rhs)
    {
        return data ~ rhs;
    }
    // lhs ~ this
    int[] opBinaryRight(string op : "~")(int lhs)
    {
        return lhs ~ data;
    }
}

void main()
{
    auto s = S([2,3]);
    assert(s ~ 4 == [2,3,4]); // opBinary
    assert(1 ~ s == [1,2,3]); // opBinaryRight
}

演算子オーバーロードは、複数の演算子に対して同時に行うことができる。 例えば、+または-演算子のみがサポートされている場合である:

T opBinary(string op)(T rhs)
{
    static if (op == "+") return data + rhs.data;
    else static if (op == "-") return data - rhs.data;
    else static assert(0, "Operator "~op~" not implemented");
}

それらを一斉に行うことだ:

T opBinary(string op)(T rhs)
{
    return mixin("data "~op~" rhs.data");
}

opIn 」と「opIn_r 」は非推奨となり、「 」と「 」が採用された。 opBinary!"in"opBinaryRight!"in"

比較演算子のオーバーロード

Dは比較演算子==,!=.のオーバーロードを許可している、 < <= opEquals ,>=,> のオーバーロードが可能である。 opCmp.

等号演算子と不等号演算子は、比較演算子とは別に扱われる。 比較演算子 とは別に扱われる。なぜなら、実質的にすべてのユーザー定義型が等式で比較できるからである。 を比較することができるが、意味のある順序を持つのは一部の型だけだからである。例えば 例えば、2つのRGBカラーベクトルが等しいかどうかを判断することは意味がある。 が等しいかどうかを判断するのは意味があるが、ある色が別の色より大きいというのは意味がない。 というのは、色には順序がないからだ。したがって opEquals Color を定義することはできるが、opCmp を定義することはできない。

さらに、順序付け可能な型であっても、順序関係は線形ではないかもしれない。 は線形ではないかもしれない。例えば、部分集合関係によって集合の順序を定義することができる。 x の(厳密な)部分集合であれば、x < y は真である。 y y の部分集合である場合、x は真である。 x < yy < x も成立しないが,だからといって x == y.したがって、純粋に opCmp だけで等しいと判断するには不十分である。このため、opCmp は、以下の場合にのみ使用される。 不等式演算子<<=>=> にのみ使用される。等式 演算子==!= は、常にopEquals を使用する。

したがって、プログラマーの責任において opCmpopEquals が一致するようにすることは、プログラマーの責任である。もし opEquals が指定されない場合、コンパイラはメンバ単位の比較を行うデフォルトのバージョン を提供する。これで十分な場合は opCmp だけを定義して、不等式演算子の振る舞いをカスタマイズすることができる。 しかし そうでない場合は、opEquals のカスタム・バージョンも定義する必要がある。 のカスタムバージョンも定義する必要がある。 のカスタムバージョンも定義する必要がある。

最後に、ユーザー定義型を組み込み連想配列のキーとして使用する場合、プログラマーは 最後に、ユーザー定義型を組み込み連想配列のキーとして使用する場合、プログラマーは、 と のセマンティクスが一致していることを保証しなければならない。 opEquals toHash のセマンティクスが一貫していることを保証しなければならない。もしそうでなければ 連想配列が期待通りに動作しない可能性がある。

オーバーロード==!=

a != b 形式の式は!(a == b) のように書き換えられる。

a == b が与えられる:

  1. aとbがともにクラスオブジェクトである場合、式は次のように書き換えられる:
    .object.opEquals(a, b)
    

    となり、その関数は次のように実装される:

    bool opEquals(Object a, Object b)
    {
        if (a is b) return true;
        if (a is null || b is null) return false;
        if (typeid(a) == typeid(b)) return a.opEquals(b);
        return a.opEquals(b) && b.opEquals(a);
    }
    
  2. そうでない場合は、a.opEquals(b)b.opEquals(a) が試される。両方が同じopEquals 関数に解決する場合、式はa.opEquals(b) に書き換えられる。
  3. 一方が他方よりよくマッチする場合、あるいは一方がコンパイルされ他方がコンパイルされない場合は、一方が選択される。 を選択する。
  4. そうでない場合はエラーとなる。

クラスに対してObject.opEquals() をオーバーライドする場合、クラス・メンバー 関数のシグネチャは次のようになる:

class C
{
    override bool opEquals(Object o) { ... }
}

構造体が同一性比較のためのopEquals メンバ関数を宣言する場合、以下のようないくつかの形式がある。 同一性比較のためのメンバ関数を宣言する場合、次のような形式がある:

struct S
{
    // lhsは変更可能なオブジェクトでなければならない
    bool opEquals(const S s) { ... }        // r値(テンポラリなど)の場合
    bool opEquals(ref const S s) { ... }    // l値(変数など)の場合

    // 両方のハンドはconstオブジェクトである
    bool opEquals(const S s) const { ... }  // r値(一時値など)の場合
}

別の方法として、1つのテンプレート化されたopEquals 関数を宣言することもできる パラメータを持つ

struct S
{
    // l値とr値の場合、
    // 両方のハンド側を暗黙的にconstに変換する
    bool opEquals()(auto ref const S s) const { ... }
}

オーバーロード<<=> 、および>=

比較演算は以下のように書き換えられる:

比較演算の書き換え
comparisonrewrite 1rewrite 2
a < ba.opCmp(b) < 0b.opCmp(a) > 0
a <= ba.opCmp(b) <= 0b.opCmp(a) >= 0
a > ba.opCmp(b) > 0b.opCmp(a) < 0
a >= ba.opCmp(b) >= 0b.opCmp(a) <= 0

両方の書き換えが試される。もし片方だけがコンパイルされれば、そちらが採用される。 両方が同じ関数に解決される場合は、最初の書き換えが行われる。 異なる関数に解決される場合は、最もよくマッチするものが使われる。 両方とも同じ関数に解決されるが、異なる関数である場合、あいまい性エラーとなる。 エラーとなる。

struct B
{
    int opCmp(int)         { return -1; }
    int opCmp(ref const S) { return -1; }
    int opCmp(ref const C) { return -1; }
}

struct S
{
    int opCmp(ref const S) { return 1; }
    int opCmp(ref B)       { return 0; }
}

struct C
{
    int opCmp(ref const B) { return 0; }
}

void main()
{
    S s;
    const S cs;
    B b;
    C c;
    assert(s > s);      // s.opCmp(s) > 0
    assert(!(s < b));   // s.opCmp(b) > 0  - S.opCmp(ref B)は完全一致である
    assert(!(b < s));   // s.opCmp(b) < 0  - S.opCmp(ref B)が完全一致する
    assert(b < cs);     // b.opCmp(s) < 0  - B.opCmp(ref const S)が完全一致する
    static assert(!__traits(compiles, b < c)); // C.opCmpとB.opcmpの両方が完全に一致する
}

クラスに対してObject.opCmp() 。 関数のシグネチャは次のようになる:

class C
{
    override int opCmp(Object o) { ... }
}

構造体がopCmp メンバ関数を宣言する場合、それは以下の形式でなければならない。 でなければならない:

struct S
{
    int opCmp(ref const S s) const { ... }
}

opCmp は不等式演算子にのみ使われることに注釈する; a == b のような式は常にopEquals を使用する。もしopCmp が定義されているが、opEquals が定義されていない場合、コンパイラーはメンバ単位の比較を行うデフォルトの が定義されているが、opEquals が定義されていない場合、コンパイラはメンバ単位の比較を行う のデフォルト・バージョンを提供する。この が定義されているが、 が定義されていない場合、コンパイラはメンバ単位の比較を実行する のデフォルト・バージョンを提供する。 opCmp と一致しない場合は、プログラマが適切なバージョンの を提供することになる。 opEquals の適切なバージョンを提供するのはプログラマ次第である。a <= b のような不等式は、a == b のような等式と矛盾した振る舞いをする。

struct S
{
    int i, j;
    int opCmp(ref const S s) const { return (i > s.i) - (i < s.i); } // jを無視する
}

S a = {2, 3};
S b = {2, 1};
S c = {3, 0};
assert(a < c);
assert(a <= b);
assert(!(a < b)); // opCmpはjを無視する
assert(a != b);   // 生成された opEqualsはiとjの両方のメンバをテストする
Best Practices: 整数の比較に の代わりに を使用する。 を使うことでオーバーフローを避けることができる。i - s.i (i > s.i) - (i < s.i)

関数呼び出し演算子オーバーロード

関数コール演算子() は、以下のようにしてオーバーロードすることができる。 opCall という名前の関数を宣言することでオーバーロードできる:

struct F
{
    int opCall();
    int opCall(int x, int y, int z);
}

void test()
{
    F f;
    int i;

    i = f();      // i = f.opCall();と同じ
    i = f(3,4,5); // i = f.opCall(3,4,5);と同じ
}

このようにして、構造体やクラスオブジェクトは、あたかもそれが関数であるかのように振る舞うことができる。 関数であるかのように振る舞うことができる。

opCall 」と宣言するだけで、構造体リテラル構文は自動的に無効になる。 構造体構文が自動的に無効になる。 この制限を避けるには コンストラクタを宣言する。 を宣言し、Type(...) 構文でopCall より優先されるようにする。

struct Multiplier
{
    int factor;
    this(int num) { factor = num; }
    int opCall(int value) { return value * factor; }
}

void main()
{
    Multiplier m = Multiplier(10);  // コンストラクタを呼び出す
    assert(m.factor == 10);
    int result = m(5);              // opCallを呼び出す
    assert(result == 50);
}

静的 opCall

static opCall を持つ関数呼び出し演算子も期待通りに機能する。 を持つ関数呼び出し演算子に対しても期待通りに動作する。

struct Double
{
    static int opCall(int x) { return x * 2; }
}
void test()
{
    int i = Double(2);
    assert(i == 4);
}

構造体コンストラクタとstatic opCall を混在させることは許されない。

struct S
{
    this(int i) {}
    static S opCall()  // コンストラクタのため許可されない
    {
        return S.init;
    }
}

注釈:static opCall を使って、引数のない構造体コンストラクタをシミュレートすることができる。 コンストラクタを引数なしでシミュレートするために使うこともできるが、これは推奨されない。 推奨されない。その代わりに、構造体インスタンスを生成するファクトリー関数 関数を使うことである。

代入演算子オーバーロード

代入演算子= は、左辺が構造体集合体である場合、オーバーロードすることができる。 左辺が構造体集合体であり、かつopAssign がその集合体のメンバ関数である場合、代入演算子をオーバーロードすることができる。

構造体型の場合、同一性代入の演算子オーバーロード に対する演算子のオーバーロードが許される。
struct S
{
    // アイデンティティ代入は許可される。
    void opAssign(S rhs);

    // 同一性代入ではなく、許可されている。
    void opAssign(int);
}
S s;
s = S();      // s.opAssign(S());に書き換えられる
s = 1;        // s.opAssign(1);に書き換えられる
しかしクラス型では、IDの割り当ては許されない。すべてのクラス すべてのクラス型は参照セマンティクスを持つので、同一性代入はデフォルトで左辺を右辺の引数に再バインドする。 これはオーバーライドできない。
class C
{
    // XがCと同じ型、またはCに暗黙的に変換可能な型である場合、
    // opAssignは同一性代入を受け入れることになるが、
    // これは許可されない。
    // C opAssign(...);
    // C opAssign(X);
    // C opAssign(X, ...);
    // C opAssign(X ...);
    // C opAssign(X, U = defaultValue, etc.);

    // アイデンティティ代入ではない
    void opAssign(int);
}
C c = new C();
c = new C();  // 参照を再バインドする
c = 1;        // c.opAssign(1);に書き換えられる

インデックス代入演算子オーバーロード

代入の左辺が構造体やクラスのインスタンスに対するインデックス操作である場合 である場合、 メンバ関数を提供することでオーバーロードできる、 これは、opIndexAssign メンバ関数を提供することでオーバーロードできる。 a[ b1,b2, ...bn] = c という形の式は、次のように書き換えられる。 a.opIndexAssign(c, b1,b2, ...bn) と書き換える。

struct A
{
    int opIndexAssign(int value, size_t i1, size_t i2);
}

void test()
{
    A a;
    a[i,3] = 7;  // a.opIndexAssign(7,i,3);と同じ
}

スライス代入演算子オーバーロード

代入の左辺が、構造体やクラスのインスタンスに対するスライス演算である場合、オーバーロードすることができる。 を実装することでオーバーロードできる。 opIndexAssign メンバ関数を実装することでオーバーロードできる。 opSlice 関数の戻り値をパラメータとするメンバ関数を実装することで、オーバーロードすることができる。 a[ i..j] = c という形の式は、次のように書き換えられる。 a.opIndexAssign(c, a.opSlice!0(i,j)) 、 と、a[] = ca.opIndexAssign(c) と書き換える。

詳細は配列インデックスとスライシング演算子オーバーロード」を参照のこと。

struct A
{
    int opIndexAssign(int v);  // a[] = vのオーバーロード
    int opIndexAssign(int v, size_t[2] slice);  // a[i .. j] = vのオーバーロード
    size_t[2] opSlice(size_t dim)(size_t i, size_t j);  // i .. jのオーバーロード
}

void test()
{
    A a;
    int v;

    a[] = v;  // a.opIndexAssign(v);と同じ
    a[3..4] = v;  // a.opIndexAssign(v, a.opSlice!0(3,4));と同じ
}

後方互換性のために、a[ i..j] を次のように書き直す。 a.opIndexAssign(a.opSlice!0(i,j)) として書き直すとコンパイルに失敗する場合は、レガシーな書き換えである opSliceAssign(c, i,j) が代わりに使われる。

Op代入演算子オーバーロード

以下のop代入演算子はオーバーロード可能である:

オーバーロード可能なop代入演算子
+=-=*=/=%=^^=&=
|=^=<<= >>=>>>=~=

式である:

a op= b

は次のように書き換えられる:

a.opOpAssign!("op")(b)
例:
struct S
{
    int i;
    void opOpAssign(string op: "+")(int rhs) { i += rhs; }
}

S s = {2};
s += 3;
assert(s.i == 5);

インデックスOpの代入演算子オーバーロード

op=の左辺が構造体またはクラスインスタンスのインデックス式で、。 であり、opIndexOpAssign がメンバである:

a[b1, b2, ... bn] op= c

と書き換えられる:

a.opIndexOpAssign!("op")(c, b1, b2, ... bn)

スライスOpの代入演算子オーバーロード

op=の左辺が構造体またはクラスインスタンスのスライス式で、。 のスライス式であり、opIndexOpAssign がメンバである:

a[i..j] op= c

と書き換えられる:

a.opIndexOpAssign!("op")(c, a.opSlice(i, j))

そして

a[] op= c

と書き換えられる:

a.opIndexOpAssign!("op")(c)

後方互換性のため、上記の書き換えが失敗し、かつ opSliceOpAssign が定義されている場合は a.opSliceOpAssign(c, i, j)a.opSliceOpAssign(c) がそれぞれ試行される。

配列のインデックスとスライス演算子オーバーロード

配列のインデックス演算子およびスライス演算子は、以下のようにオーバーロードされる。 opIndexopSliceopDollar メソッドを実装する。 これらを組み合わせて多次元配列を実装することもできる。

インデックス演算子オーバーロード

arr[b1,b2, ...bn] という形の式は、 b1, b2, ... bn に変換される。 arr.opIndex( b1,b2, ...bn) に変換される。例えば、次のようになる:

struct A
{
    int opIndex(size_t i1, size_t i2, size_t i3);
}

void test()
{
    A a;
    int i;
    i = a[5,6,7];  // i = a.opIndex(5,6,7);と同じ
}

このようにして、構造体やクラス・オブジェクトは、あたかも配列であるかのように振る舞うことができる。 配列のように振る舞うことができる。

インデックス式がopIndexAssign または opIndexOpAssign で書き直せる場合は、opIndex よりもそちらを優先する。

スライス演算子オーバーロード

スライス演算子のオーバーロードとは、次のような式のオーバーロードを意味する。 a[]a[ i..j] のような式をオーバーロードすることである。 の中の式はi..jの形のスライス式を含む。

a[] をオーバーロードするには、パラメータなしでopIndex を定義する:

struct S
{
    int[] impl;
    int[] opIndex()
    {
        return impl[];
    }
}

void main()
{
    auto s = S([1,2,3]);
    int[] t = s[]; // s.opIndex()を呼び出す
    assert(t == [1,2,3]);
}

a[ i..j] という形の配列スライスをオーバーロードするには、2つのステップが必要である、 つのステップが必要である。 まず、i..jの形の式が、 を介してオブジェクトに変換される。 opSlice!0 をカプセル化したオブジェクトに変換する。 次に、これらのオブジェクトが に渡される。 opIndex に渡され、実際のスライシングが実行される。

struct S
{
    int[] impl;

    int[] opSlice(size_t dim: 0)(size_t i, size_t j)
    {
        return impl[i..j];
    }
    int[] opIndex()(int[] slice) { return slice; }
}

void main()
{
    auto s = S([1, 2, 3]);
    int[] t = s[0..2]; // s.opIndex(s.opSlice(0, 2))を呼び出す
    assert(t == [1, 2]);
}

このデザインは 多次元配列のインデックスとスライスの混在をサポートするために選ばれた。 多次元配列のインデックスとスライスの混在をサポートするために、この設計が選ばれた。 arr[1, 2..3, 4]. ??り正確には、arr[b1, b2, ... bnの形の式は、 c1, c2, ... cnに変換される。] は、arr.opIndex(c1,c2, ...cn) に変換される。 各引数biは1つの式である、 この場合、対応する引数ciとして opIndex に直接渡される。 xi..yi という形式のスライス式である。 この場合、opIndex への対応する引数ciarr.opSlice!i(xi, yi) である。つまり、次のようになる:

oprewrite
arr[1, 2, 3] arr.opIndex(1, 2, 3)
arr[1..2, 3..4, 5..6] arr.opIndex(arr.opSlice!0(1,2), arr.opSlice!1(3,4), arr.opSlice!2(5,6))
arr[1, 2..3, 4] arr.opIndex(1, arr.opSlice!1(2,3), 4)

同様の変換は、スライスを含む代入演算子に対しても行われる。 例えば、スライシングを含む代入演算子についても同様の変換が行われる:

oprewrite
arr[1, 2..3, 4] = c arr.opIndexAssign(c, 1, arr.opSlice!1(2, 3), 4)
arr[2, 3..4] += c arr.opIndexOpAssign!"+"(c, 2, arr.opSlice!1(2, 3))

その意図は、opSlice!i 、 '番目のインデックスに沿った区間を表す、ユーザー定義のオブジェクトを返すべきであるということである。 i オブジェクトを返すことである。 次元に沿ったインデックスの間隔を表すユーザー定義オブジェクトを返す。このオブジェクトはopIndex 。 に渡される。 一次元のスライスのみが必要な場合 1次元のスライスのみが必要な場合、opSlice はコンパイル時のパラメータなしで宣言することができる。 パラメータi を使わずに宣言することもできる。

すべての場合において、arr は一度しか評価されないことに注釈:。したがって getArray()[1, 2..3, $-1]=c のような式は、次のような効果を持つ:

auto __tmp = getArray();
__tmp.opIndexAssign(c, 1, __tmp.opSlice!1(2,3), __tmp.opDollar!2 - 1);

ここで、getArray への最初の関数呼び出しは一度しか実行されない。 を一度だけ実行する。

注釈:後方互換性のため、a[]a[ i..j] は、引数なしで を実装することによってオーバーロードすることもできる。 opSlice() をオーバーロードすることもできる。 opSlice(i,j) を2つの引数で実装することでオーバーロードすることもできる、 をそれぞれオーバーロードすることもできる。 これは1次元スライスにのみ適用される。 Dが多次元配列を完全にサポートしていなかった頃からのものである。この opSlice

ドル演算子オーバーロード

配列のインデックス演算子やスライス演算子の引数の中で$ iopDollar!i に変換される。 式$ の位置である。例:

oprewrite
arr[$-1, $-2, 3] arr.opIndex(arr.opDollar!0 - 1, arr.opDollar!1 - 2, 3)
arr[1, 2, 3..$] arr.opIndex(1, 2, arr.opSlice!2(3, arr.opDollar!2))

その意図は、opDollar!i 、配列の長さを返すことである。 i または、その次元に沿った配列の終端を表すユーザー定義オブジェクトを返す。 が理解できる、その次元に沿った配列の終端を表すユーザー定義オブジェクトを返すことである。 opSlice opIndex によって理解される。

struct Rectangle
{
    int width, height;
    int[][] impl;
    this(int w, int h)
    {
        width = w;
        height = h;
        impl = new int[w][h];
    }
    int opIndex(size_t i1, size_t i2)
    {
        return impl[i1][i2];
    }
    int opDollar(size_t pos)()
    {
        static if (pos==0)
            return width;
        else
            return height;
    }
}

void test()
{
    auto r = Rectangle(10,20);
    int i = r[$-1, 0];    // r.opIndex(r.opDollar!0,0)と同じ、
                          // これはr.opIndex(r.width-1, 0)である
    int j = r[0, $-1];    // r.opIndex(0, r.opDollar!1)と同じ。
                          // これはr.opIndex(0, r.height-1)である
}

上の例が示すように、コンパイル時の引数は異なる。 opDollar に渡される。A $opDollar!0 に変換される、 第2引数に現れた$ は に変換される。 はopDollar!1 に変換される。の適切な値を返すことができる。$ に適切な値を返すことで、多次元配列を実装することができる。

opDollar!ii $ に対して一度だけ評価されることに注意されたい。 に対して一度だけ評価される。 したがって、arr[$-sqrt($), 0, $-1] のような式は次のような効果を持つ。 の効果を持つ:

auto __tmp1 = arr.opDollar!0;
auto __tmp2 = arr.opDollar!2;
arr.opIndex(__tmp1 - sqrt(__tmp1), 0, __tmp2 - 1);

opIndex が引数1つだけで宣言されている場合、 へのコンパイル時引数は省略できる。 opDollar のコンパイル時引数は省略できる。この場合 $ を複数の引数を持つ配列インデックス式の内部で使用することは違法である。 この場合、2つ以上の引数を持つ配列インデックス式の内部で を使用することは違法である。

完全な例

以下のコード例は、2次元配列の簡単な実装を示している。 2次元配列をオーバーロードされたインデックスとスライス演算子で実装したものである。使用されている 採用されているさまざまな構成要素の説明は、以下のセクションで行う。 で説明する。

struct Array2D(E)
{
    E[] impl;
    int stride;
    int width, height;

    this(int width, int height, E[] initialData = [])
    {
        impl = initialData;
        this.stride = this.width = width;
        this.height = height;
        impl.length = width * height;
    }

    // 単一の要素にインデックスを付ける、例えば、arr[0, 1]
    ref E opIndex(int i, int j) { return impl[i + stride*j]; }

    // 配列のスライス、例えばarr[1..2, 1..2]、arr[2, 0..$]、arr[0..$, 1]。
    Array2D opIndex(int[2] r1, int[2] r2)
    {
        Array2D result;

        auto startOffset = r1[0] + r2[0]*stride;
        auto endOffset = r1[1] + (r2[1] - 1)*stride;
        result.impl = this.impl[startOffset .. endOffset];

        result.stride = this.stride;
        result.width = r1[1] - r1[0];
        result.height = r2[1] - r2[0];

        return result;
    }
    auto opIndex(int[2] r1, int j) { return opIndex(r1, [j, j+1]); }
    auto opIndex(int i, int[2] r2) { return opIndex([i, i+1], r2); }

    // 与えられた次元のスライス演算子で`x..y`表記をサポートする。
    int[2] opSlice(size_t dim)(int start, int end)
        if (dim >= 0 && dim < 2)
    in { assert(start >= 0 && end <= this.opDollar!dim); }
    do
    {
        return [start, end];
    }

    // スライス表記で`$`をサポート、例えば、arr[1..$, 0..$-1]。
    @property int opDollar(size_t dim : 0)() { return width; }
    @property int opDollar(size_t dim : 1)() { return height; }
}

void main()
{
    auto arr = Array2D!int(4, 3, [
        0, 1, 2,  3,
        4, 5, 6,  7,
        8, 9, 10, 11
    ]);

    // 基本のindexing
    assert(arr[0, 0] == 0);
    assert(arr[1, 0] == 1);
    assert(arr[0, 1] == 4);

    // opDollarの使用
    assert(arr[$-1, 0] == 3);
    assert(arr[0, $-1] == 8);   // 次元によって$の値が異なることに注意する
    assert(arr[$-1, $-1] == 11);

    // スライス
    auto slice1 = arr[1..$, 0..$];
    assert(slice1[0, 0] == 1 && slice1[1, 0] == 2  && slice1[2, 0] == 3 &&
           slice1[0, 1] == 5 && slice1[1, 1] == 6  && slice1[2, 1] == 7 &&
           slice1[0, 2] == 9 && slice1[1, 2] == 10 && slice1[2, 2] == 11);

    auto slice2 = slice1[0..2, 1..$];
    assert(slice2[0, 0] == 5 && slice2[1, 0] == 6 &&
           slice2[0, 1] == 9 && slice2[1, 1] == 10);

    // 薄いスライス
    auto slice3 = arr[2, 0..$];
    assert(slice3[0, 0] == 2 &&
           slice3[0, 1] == 6 &&
           slice3[0, 2] == 10);

    auto slice4 = arr[0..3, 2];
    assert(slice4[0, 0] == 8 && slice4[1, 0] == 9 && slice4[2, 0] == 10);
}

転送

クラスや構造体の中に見つからないメンバー名は、 というテンプレート関数に転送して解決することができる。 opDispatch というテンプレート化された関数に転送することができる。

import std.stdio;

struct S
{
    void opDispatch(string s, T)(T i)
    {
        writefln("S.opDispatch('%s', %s)", s, i);
    }
}

class C
{
    void opDispatch(string s)(int i)
    {
        writefln("C.opDispatch('%s', %s)", s, i);
    }
}

struct D
{
    template opDispatch(string s)
    {
        enum int opDispatch = 8;
    }
}

void main()
{
    S s;
    s.opDispatch!("hello")(7);
    s.foo(7);

    auto c = new C();
    c.foo(8);

    D d;
    writefln("d.foo = %s", d.foo);
    assert(d.foo == 8);
}

D1スタイルの演算子オーバーロード

D1演算子オーバーロードのメカニズム は非推奨である。