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