Wrong selection of opEquals for objects.

Simen Kjærås simen.kjaras at gmail.com
Fri Aug 28 12:29:20 UTC 2020


On Friday, 28 August 2020 at 10:42:09 UTC, Alexandru Ermicioi 
wrote:
> No that is not a solution at all, in template code that 
> requires safety. You basically will have to sacrifice safety 
> for rest of types, such as structs, unions & enums for the sake 
> of objects being able to compare.

Yup. There's a reason I'm saying it's suboptimal. FWIW, you can 
limit the taint by using @trusted lambdas.

One of the reasons this hasn't been fixed is idiomatic D code 
very rarely uses classes, but that does not mean they're always 
the wrong tool.


> Could we just template that opEquals in this manner:
> -------
> bool opEquals(T : Object, X : Object)(T lhs, X rhs)
> {
>     if (lhs is rhs) return true;
>
>     if (lhs is null || rhs is null) return false;
>
>     if (!lhs.opEquals(rhs)) return false;
>
>     if (typeid(lhs) is typeid(rhs) ||
>         !__ctfe && typeid(lhs).opEquals(typeid(rhs)))
>     {
>         return true;
>     }
>
>     return rhs.opEquals(lhs);
> }
> -------
>
> That would at least allow us to define an overload which is 
> safe and would be picked up by new implementation.
>
> I'm wondering why it wasn't done yet, are there any reasons for 
> that?

The template solution may look like it solves every problem, but 
it really doesn't. Consider this code:

class A {
     bool opEquals(A) { return false; }
}
class B : A {
     bool opEquals(B) { return true; }
}

unittest {
     B b1 = new B();
     B b2 = new B();
     A a1 = b1;
     A a2 = b2;
     assert(b1 == b2);
     assert(a1 != a2); // WTF?
}

With the template solution, the function in B would be ignored 
when stored in a variable with static type A. This solution would 
sometimes do the right thing, other times it would silently do 
the wrong thing.




> Also, why it is limited to just objects? It seems that this 
> function enforces symmetry between two objects. What about rest 
> of the possible types, such as structs, unions?

That's an issue. The spec clearly states 
(https://dlang.org/spec/operatoroverloading.html#equals):

2. [T]he expressions a.opEquals(b) and b.opEquals(a) are tried. 
If both resolve to the same opEquals function, then the 
expression is rewritten to be a.opEquals(b).
3. If one is a better match than the other, or one compiles and 
the other does not, the first is selected.
4. Otherwise, an error results.

This is clearly not the case:

struct S1 {
     bool opEquals(S2 a) {
         return true;
     }
}
struct S2 {
     bool opEquals(S1 a) {
         return false;
     }
}

unittest {
     S1 a;
     S2 b;
     assert((a == b) == (b == a)); // Fails
}

I didn't find a bugzilla entry on this, but I'm pretty sure there 
is one.

As for why there's no global function like for classes, that's 
because there's no need - there is no disconnect between the 
static and dynamic types of non-class variables (or, if there is, 
it's explicitly programmed in, like in std.variant.Variant).

--
   Simen


More information about the Digitalmars-d-learn mailing list