Thread overview
interface opEquals
Nov 23, 2023
Antonio
Nov 23, 2023
Jonathan M Davis
Nov 24, 2023
Antonio
Nov 25, 2023
Alexandru Ermicioi
Nov 27, 2023
Antonio
Nov 27, 2023
Alexandru Ermicioi
November 23, 2023
interface IOpt(T)
{
  T value();
  bool empty();
  bool opEquals(IOpt!T other);
}

class None(T) : IOpt!T
{
  bool empty() => true;
  T value(){ throw new Exception("None has not a value"); }
  bool opEquals(IOpt!T other)=>other.empty;
}

class Some(T) : IOpt!T
{
  this(T value)  { this._value = value; }
  bool empty() => false;
  T value()=> _value;
  bool opEquals(IOpt!T other)=>!other.empty && other.value==_value;

  private T _value;
}

IOpt!T some(T)(T v)=>new Some!T(v);
IOpt!T none(T)()=>new None!T;

void main()
{
  assert(new Some!int(1) == new Some!int(1));
  assert(new None!int == new None!int);
  assert(none!int.opEquals(none!int));
  assert(none!int == none!int);
}

It compiles, but last assertion assert(none!int == none!int); fails

core.exception.AssertError@testiface.d(33): Assertion failure

To avoid "extrange effects" I test an alternative equality that fails too:

  assert( (cast (IOpt!int) new None!int) == (cast (IOpt!int) new None!int));

What seems strange to me is that none!int.opEquals(none!int) works properly.

Questions

  • Why, when applied to interface, opEquals called directly behavior is not the same that when calling == ?

  • Is it the expected behaviour?

November 23, 2023
On Thursday, November 23, 2023 2:20:25 PM MST Antonio via Digitalmars-d-learn wrote:

> * Why, when applied to interface, ```opEquals``` called directly behavior is not the same that when calling ```==``` ?
>
> * Is it the expected behaviour?

I'd have to take the time to study your code in detail to see whether what exactly you're seeing makes sense or not, but it's not expected that normal D code will call opEquals directly, and for classes, == does more than call lhs.opEquals(rhs). It does additional stuff to try to have the correct behavior for equality without you having to code it all up yourself in opEquals.

== on classes results in the free function, opEquals, in object.d being called. That function does a variety of checks such as checking whether either reference is null (to avoid dereferencing null) and using is to compare the address of the class references first (to avoid calling the class' opEquals if both references are to the same object). It also makes sure that both rhs.opEquals(lhs) and lhs.opEquals(rhs) are true for == to be true to avoid subtle bugs that can come into play when comparing a base class against a derived class.

https://github.com/dlang/dmd/blob/master/druntime/src/object.d#L269

- Jonathan M Davis



November 24, 2023

On Thursday, 23 November 2023 at 21:52:56 UTC, Jonathan M Davis wrote:

>

I'd have to take the time to study your code in detail to see whether what exactly you're seeing makes sense or not ...

My apologies... I should have proposed a more specific example.

interface IOpt {  bool opEquals(IOpt other) const @safe pure;  }

class None : IOpt { bool opEquals(IOpt other) const @safe pure => true; }

void main() {
  None
    a = new None(),
    b = new None();

  IOpt
    a2 = a,
    b2 = b;

  assert(a == b);
  assert(a2 == a);
  assert(b2 == b);
  assert(a2 == b2); // fails!!!
}

>

== on classes results in the free function, opEquals, in object.d being called. That function does a variety of checks such as checking whether either reference is null (to avoid dereferencing null) and using is to compare the address of the class references first (to avoid calling the class' opEquals if both references are to the same object). It also makes sure that both rhs.opEquals(lhs) and lhs.opEquals(rhs) are true for == to be true to avoid subtle bugs that can come into play when comparing a base class against a derived class.

https://github.com/dlang/dmd/blob/master/druntime/src/object.d#L269

Replacing interface by abstract class removes the problem. But I have not enough D knowledge to discover why interface version is not working

Thanks
Antonio

November 25, 2023

On Friday, 24 November 2023 at 17:39:10 UTC, Antonio wrote:

>

...

Dunno if this might help, but I noticed that == sometimes calls opEquals(const Object) const instead of overload defined on class/interface, you might try and override it as well, and delegate to your overload that deals directly with IOpt.

Best regards,
Alexandru.

November 27, 2023

On Saturday, 25 November 2023 at 01:15:34 UTC, Alexandru Ermicioi wrote:

>

On Friday, 24 November 2023 at 17:39:10 UTC, Antonio wrote:

>

...

Dunno if this might help, but I noticed that == sometimes calls opEquals(const Object) const instead of overload defined on class/interface, you might try and override it as well, and delegate to your overload that deals directly with IOpt.

Best regards,
Alexandru.

Thangs Alexandru,

It works.

...but why?

November 27, 2023

On Monday, 27 November 2023 at 09:53:48 UTC, Antonio wrote:

>

...but why?

All classes (and interfaces I think), at root of inheritance have Object class, this class defines couple of generic methods, you can find this class in object.d btw. One of those methods is bool opEquals(const Object rhs) const, therefore if you try to call opEquals, it could be possible that in some cricumstances instead of your overload, one defined on Object is picked up, which checks only if rhs is same object as the one invoked upon.

Btw, dunno how the rules for overload set resolution on opEquals work, so someone else should check whther this is a bug or expected behavior.

Best regards,
Alexandru.