opEquals

Implementation for class opEquals override. Calls the class-defined methods after a null check. Please note this is not nogc right now, even if your implementation is, because of the typeinfo name string compare. This is because of dmd's dll implementation. However, it can infer to @safe if your class' opEquals is.

bool
opEquals
(
LHS
RHS
)
(
LHS lhs
,
RHS rhs
)
if (
is(LHS : const Object) &&
is(RHS : const Object)
)

Examples

If aliased to the same object or both null => equal

class F { int flag; this(int flag) { this.flag = flag; } }

F f;
assert(f == f); // both null
f = new F(1);
assert(f == f); // both aliased to the same object

If either is null => non-equal

class F { int flag; this(int flag) { this.flag = flag; } }
F f;
assert(!(new F(0) == f));
assert(!(f == new F(0)));

If same exact type => one call to method opEquals This test passes @safe because it defines a new opEquals with @safe

class F
{
    int flag;

    this(int flag)
    {
        this.flag = flag;
    }

    bool opEquals(const F o) const @safe nothrow pure
    {
        return flag == o.flag;
    }
}

F f;
assert(new F(0) == new F(0));
assert(!(new F(0) == new F(1)));

General case => symmetric calls to method opEquals

int fEquals, gEquals;

class Base
{
    int flag;
    this(int flag)
    {
        this.flag = flag;
    }
}

class F : Base
{
    this(int flag) { super(flag); }

    bool opEquals(const Base o) @safe
    {
        fEquals++;
        return flag == o.flag;
    }
}

class G : Base
{
    this(int flag) { super(flag); }

    bool opEquals(const Base o) @safe
    {
        gEquals++;
        return flag == o.flag;
    }
}

assert(new F(1) == new G(1));
assert(fEquals == 1);
assert(gEquals == 1);

This test shows an example for a comprehensive inheritance equality chain too.

1 static class Base
2 {
3     int member;
4 
5     this(int member) pure @safe nothrow @nogc
6     {
7         this.member = member;
8     }
9 
10     override bool opEquals(Object rhs) const
11     {
12         return this.opEquals(cast(Base) rhs);
13     }
14 
15     bool opEquals(const Base rhs) const @nogc pure nothrow @safe
16     {
17         if (rhs is null)
18             return false;
19         return this.member == rhs.member;
20     }
21 }
22 
23 // works through the direct class with attributes enabled, except for pure and nogc in the current TypeInfo implementation
24 bool testThroughBase() nothrow @safe
25 {
26     Base b1 = new Base(0);
27     Base b2 = new Base(0);
28     assert(b1 == b2);
29     Base b3 = new Base(1);
30     assert(b1 != b3);
31     return true;
32 }
33 
34 static assert(testThroughBase());
35 
36 // also works through the base class interface thanks to the override, but no more attributes
37 bool testThroughObject()
38 {
39     Object o1 = new Base(0);
40     Object o2 = new Base(0);
41     assert(o1 == o2);
42     Object o3 = new Base(1);
43     assert(o1 != o3);
44     return true;
45 }
46 
47 static assert(testThroughObject());
48 
49 // Each time you make a child, you want to override all old opEquals
50 // and add a new overload for the new child.
51 static class Child : Base
52 {
53     int member2;
54 
55     this(int member, int member2) pure @safe nothrow @nogc
56     {
57         super(member);
58         this.member2 = member2;
59     }
60 
61     // override the whole chain so it works consistently though any base
62     override bool opEquals(Object rhs) const
63     {
64         return this.opEquals(cast(Child) rhs);
65     }
66     override bool opEquals(const Base rhs) const
67     {
68         return this.opEquals(cast(const Child) rhs);
69     }
70     // and then add the new overload, if necessary, to handle new members
71     bool opEquals(const Child rhs) const @nogc pure nothrow @safe
72     {
73         if (rhs is null)
74             return false;
75         // can call back to the devirtualized base test with implicit conversion
76         // then compare the new member too. or we could have just compared the base
77         // member directly here as well.
78         return Base.opEquals(rhs) && this.member2 == rhs.member2;
79     }
80 
81     // a mixin template, of course, could automate this.
82 }
83 
84 bool testThroughChild()
85 {
86     Child a = new Child(0, 0);
87     Child b = new Child(0, 1);
88     assert(a != b);
89 
90     Base ba = a;
91     Base bb = b;
92     assert(ba != bb);
93 
94     Object oa = a;
95     Object ob = b;
96     assert(oa != ob);
97 
98     return true;
99 }
100 
101 static assert(testThroughChild());

Meta