www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - how to make '==' safe for classes?

reply ikod <geller.garry gmail.com> writes:
Hello

How to make this code to compile? My goal is safe(not  trusted) 
longFunction().


---
class C
{
     override bool opEquals(Object o) const  safe
     {
         return true;
     }
}
bool longFunction(C a, C b)  safe
{
     return a==b;
}
void main()
{
}
---


As far as I understand the problem is that AST for this code 
looks like

---
import object;
class C : Object
{
	override const  safe bool opEquals(Object o)
	{
		return true;
	}
}
bool longFunction(C a, C b)
{
	return opEquals(a, b);
}
void main()
{
	return 0;
}
RTInfo!(C)
{
	enum typeof(null) RTInfo = null;

}
---

and object.opEquals(a,b) do not inherits safety from class C 
properties, and also I can't override it.

Is there clean way to use '==' here, or I have to convert this to 
a.opEquals(b) for classes, leaving '==' for structs?

Thanks!
Oct 28 2018
next sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Sunday, 28 October 2018 at 12:38:12 UTC, ikod wrote:

 and object.opEquals(a,b) do not inherits safety from class C 
 properties, and also I can't override it.
Yep. Since Object is the base class and it defines opEquals as: ``` bool opEquals(Object); ``` the compiler rewrites `a == b` as `(cast(Object)a).opEquals(cast(Object)ob)`, i.e. it inserts a system call into your code.
 Is there clean way to use '==' here, or I have to convert this 
 to a.opEquals(b) for classes, leaving '==' for structs?
Pretty much, yes. "Implicit" value comparison in general is somewhat alien for classes, since they're reference types.
Oct 28 2018
next sibling parent reply Neia Neutuladh <neia ikeran.org> writes:
On Sun, 28 Oct 2018 18:00:06 +0000, Stanislav Blinov wrote:

 On Sunday, 28 October 2018 at 12:38:12 UTC, ikod wrote:
 
 and object.opEquals(a,b) do not inherits safety from class C
 properties, and also I can't override it.
Yep. Since Object is the base class and it defines opEquals as: ``` bool opEquals(Object); ``` the compiler rewrites `a == b` as `(cast(Object)a).opEquals(cast(Object)ob)`, i.e. it inserts a system call into your code.
More pedantically, it rewrites it as: (a is b) || (a !is null && (cast(Object)a).opEquals(cast(Object)b)) An object might not be equal to itself via opEquals, but it will always compare equal to itself with ==.
Oct 28 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Sunday, October 28, 2018 12:17:41 PM MDT Neia Neutuladh via Digitalmars-
d-learn wrote:
 On Sun, 28 Oct 2018 18:00:06 +0000, Stanislav Blinov wrote:
 On Sunday, 28 October 2018 at 12:38:12 UTC, ikod wrote:
 and object.opEquals(a,b) do not inherits safety from class C
 properties, and also I can't override it.
Yep. Since Object is the base class and it defines opEquals as: ``` bool opEquals(Object); ``` the compiler rewrites `a == b` as `(cast(Object)a).opEquals(cast(Object)ob)`, i.e. it inserts a system call into your code.
More pedantically, it rewrites it as: (a is b) || (a !is null && (cast(Object)a).opEquals(cast(Object)b)) An object might not be equal to itself via opEquals, but it will always compare equal to itself with ==.
Technically, it calls the free function opEquals in object.d, which does more than that (including introduce a hack to work around the type system to allow comparing const objects). Specifically, the current implementation is bool opEquals(Object lhs, Object rhs) { // If aliased to the same object or both null => equal if (lhs is rhs) return true; // If either is null => non-equal if (lhs is null || rhs is null) return false; // If same exact type => one call to method opEquals if (typeid(lhs) is typeid(rhs) || !__ctfe && typeid(lhs).opEquals(typeid(rhs))) /* CTFE doesn't like typeid much. 'is' works, but opEquals doesn't (issue 7147). But CTFE also guarantees that equal TypeInfos are always identical. So, no opEquals needed during CTFE. */ { return lhs.opEquals(rhs); } // General case => symmetric calls to method opEquals return lhs.opEquals(rhs) && rhs.opEquals(lhs); } /************************ * Returns true if lhs and rhs are equal. */ bool opEquals(const Object lhs, const Object rhs) { // A hack for the moment. return opEquals(cast()lhs, cast()rhs); } - Jonathan M Davis
Oct 28 2018
prev sibling parent reply ikod <geller.garry gmail.com> writes:
On Sunday, 28 October 2018 at 18:00:06 UTC, Stanislav Blinov 
wrote:
 On Sunday, 28 October 2018 at 12:38:12 UTC, ikod wrote:

 and object.opEquals(a,b) do not inherits safety from class C 
 properties, and also I can't override it.
Yep. Since Object is the base class and it defines opEquals as:
object.opEquals(a,b) even is not a Object member function, it's free function. It looks a bit unnatural for me, but thanks for confirmation!
 ```
 bool opEquals(Object);
 ```

 the compiler rewrites `a == b` as 
 `(cast(Object)a).opEquals(cast(Object)ob)`, i.e. it inserts a 
  system call into your code.

 Is there clean way to use '==' here, or I have to convert this 
 to a.opEquals(b) for classes, leaving '==' for structs?
Pretty much, yes. "Implicit" value comparison in general is somewhat alien for classes, since they're reference types.
Oct 28 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Sunday, October 28, 2018 12:56:10 PM MDT ikod via Digitalmars-d-learn 
wrote:
 On Sunday, 28 October 2018 at 18:00:06 UTC, Stanislav Blinov

 wrote:
 On Sunday, 28 October 2018 at 12:38:12 UTC, ikod wrote:
 and object.opEquals(a,b) do not inherits safety from class C
 properties, and also I can't override it.
Yep. Since Object is the base class and it defines opEquals as:
object.opEquals(a,b) even is not a Object member function, it's free function. It looks a bit unnatural for me, but thanks for confirmation!
It may seem weird at first, but it actually solves several problems that you get when simple calling a.obEquals(b) - the most obvious of which is that a.opEquals(b) has to worry about null, which opEquals(a, b) solves for you, but if you look at the implementation I posted elsewhere in the thread, it solves some other problems as well. It's one of those places where D was able to learn from problems that languages that came before it had. Unfortunately, the attribute problem is _not_ one of those areas. - Jonathan M Davis
Oct 28 2018
prev sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Sunday, October 28, 2018 6:38:12 AM MDT ikod via Digitalmars-d-learn 
wrote:
 Hello

 How to make this code to compile? My goal is safe(not  trusted)
 longFunction().


 ---
 class C
 {
      override bool opEquals(Object o) const  safe
      {
          return true;
      }
 }
 bool longFunction(C a, C b)  safe
 {
      return a==b;
 }
 void main()
 {
 }
 ---


 As far as I understand the problem is that AST for this code
 looks like

 ---
 import object;
 class C : Object
 {
   override const  safe bool opEquals(Object o)
   {
       return true;
   }
 }
 bool longFunction(C a, C b)
 {
   return opEquals(a, b);
 }
 void main()
 {
   return 0;
 }
 RTInfo!(C)
 {
   enum typeof(null) RTInfo = null;

 }
 ---

 and object.opEquals(a,b) do not inherits safety from class C
 properties, and also I can't override it.

 Is there clean way to use '==' here, or I have to convert this to
 a.opEquals(b) for classes, leaving '==' for structs?

 Thanks!
Because Object predats safe (and most attributes), it really isn't compatible with them. In fact, it predates const (since it's basically the same as it was in D1) and it's only possible to compare const class objects because of a hack in the free function opEquals (which == lowers to) which casts away const (meaning that if you're not careful, you can actually violate the type system with == by mutating an object in a class' opEquals). And because attributes are inherited, even if we were willing to break existing code by changing the attributes on Object, there really isn't a good way to fix the problem, because whatever set of attributes we picked (for safe, nothrow, const, etc.) would work for some programs but not others. That's why at one point, it was decided that we would remove all of the various member functions from Object. Given the templates in D, they really aren't necessary like they are in languages like Java. As long as stuff like the built in AA implementation is templated (which it unfortunately is not right now), all of the appropriate information can be inferred, and it's not necessary to have a root class object with member functions like opEquals in order to use it in generic code. https://issues.dlang.org/show_bug.cgi?id=9769 https://issues.dlang.org/show_bug.cgi?id=9770 https://issues.dlang.org/show_bug.cgi?id=9771 https://issues.dlang.org/show_bug.cgi?id=9772 However, while that decision was made some time ago, actually implementing it isn't easy, and the necessary steps have never happened - to the point that it doesn't seem very likely at this point. What seems far more likely is a DIP that Andrei has proposed: https://github.com/andralex/DIPs/blob/ProtoObject/DIPs/DIPxxxx.md It will introduce ProtoObject as a new root object below Object which does not have any member functions or an embedded monitor object (which is only necessary if you actually have synchronized functions). Object would stay the default base class (since code would break otherwise), and any code using Object would unfortunately continue to have the same problems, but classes that then explicitly derive from ProtoObject would be able to define opEquals, opCmp, toString, etc. with the signatures that were appropriate to the applications or libraries that they're in. Any classes derived from such classes would then be stuck with those attributes just like we're stuck with the attributes on Object right now, but those choices would then be per object hierarchy rather than forced on everyone using the language. So, it looks like that's probably going to be the ultimate fix for this problem, but we don't really have an ETA at the moment. So, unfortunately, for now, you're going to have to use trusted with == on classes, as stupid as that is, though as a workaround, you could always create an trusted wrapper function that just called ==. It would still be annoying, but it would be cleanly restricted the trusted bits. - Jonathan M Davis
Oct 28 2018
parent ikod <geller.garry gmail.com> writes:
Thanks for excellent explanation!

On Sunday, 28 October 2018 at 19:00:52 UTC, Jonathan M Davis 
wrote:
 Because Object predats  safe (and most attributes), it really 
 isn't compatible with them. In fact, it predates const (since 
 it's basically the same as it was in D1) and it's only possible 
 to compare const class objects because of a hack in the free 
 function opEquals (which == lowers to) which casts away const 
 (meaning that if you're not careful, you can actually violate 
 the type system with == by mutating an object in a class'
...
 So, unfortunately, for now, you're going to have to use 
  trusted with == on classes, as stupid as that is, though as a 
 workaround, you could always create an  trusted wrapper 
 function that just called ==. It would still be annoying, but 
 it would be cleanly restricted the  trusted bits.

 - Jonathan M Davis
Oct 28 2018