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());
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.