www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Behavior of opEquals

reply Jacob Carlborg <doob me.com> writes:
I encountered a problem in the implementation of 
std.xml.Document.opEquals (yes, I've reported an issue). The problem is 
demonstrated with this example:

class Base
{
     int a;

     override bool opEquals (Object o)
     {
         if (auto base = cast(Base) o)
             return base.a == a;
         else
             return false;
     }
}

class Foo : Base
{
     int b;

     override bool opEquals (Object o)
     {
         if (auto foo = cast(Foo) o)
             return super == cast(Base) foo && foo.b == b;
         else
             return false;
     }
}

void main()
{
     auto f1 = new Foo;
     auto f2 = new Foo;
     assert(f1 == f2);
}

This code will result in an infinite recursion. I think the problem is 
in the super call, due to == being rewritten to call object.opEquals. 
The implementation of object.opEquals will call opEquals on the actual 
instances. The call will be dynamically resolved and end up calling 
Foo.opEquals instead of Base.opEquals.

Is this really good behavior, something a developer would expect? I 
mean, in every other case calling super.someMethod will actually call 
the method in the base class.

In this case the solution/workaround is to explicitly call 
super.opEquals, but that will miss some optimizations implemented in 
object.opEquals.

-- 
/Jacob Carlborg
Sep 02 2015
next sibling parent reply "w0rp" <devw0rp gmail.com> writes:
On Wednesday, 2 September 2015 at 18:57:11 UTC, Jacob Carlborg 
wrote:
 I encountered a problem in the implementation of 
 std.xml.Document.opEquals (yes, I've reported an issue). The 
 problem is demonstrated with this example:

 class Base
 {
     int a;

     override bool opEquals (Object o)
     {
         if (auto base = cast(Base) o)
             return base.a == a;
         else
             return false;
     }
 }

 class Foo : Base
 {
     int b;

     override bool opEquals (Object o)
     {
         if (auto foo = cast(Foo) o)
             return super == cast(Base) foo && foo.b == b;
         else
             return false;
     }
 }

 void main()
 {
     auto f1 = new Foo;
     auto f2 = new Foo;
     assert(f1 == f2);
 }

 This code will result in an infinite recursion. I think the 
 problem is in the super call, due to == being rewritten to call 
 object.opEquals. The implementation of object.opEquals will 
 call opEquals on the actual instances. The call will be 
 dynamically resolved and end up calling Foo.opEquals instead of 
 Base.opEquals.

 Is this really good behavior, something a developer would 
 expect? I mean, in every other case calling super.someMethod 
 will actually call the method in the base class.

 In this case the solution/workaround is to explicitly call 
 super.opEquals, but that will miss some optimizations 
 implemented in object.opEquals.
Yeah, I would just call super.opEquals, like so. class Base { int a; override bool opEquals(Object o) { if (auto other = cast(Base) o) return a == other.a; return false; } } class Foo : Base { int b; override bool opEquals(Object o) { if (!super.opEquals(o)) return false; if (auto other = cast(Foo) o) return b == other.b; return false; } } void main() { auto f1 = new Foo; auto f2 = new Foo; assert(f1 == f2); } If some optimisations are missed by structuring the methods in this way, then maybe that's something the compiler should be programmed to handle.
Sep 02 2015
parent reply Jacob Carlborg <doob me.com> writes:
On 2015-09-02 22:25, w0rp wrote:

 Yeah, I would just call super.opEquals, like so.
I know that's the workaround, but the question is if it's a good implementation/behavior of opEquals. -- /Jacob Carlborg
Sep 02 2015
parent "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= writes:
On Thursday, 3 September 2015 at 06:37:20 UTC, Jacob Carlborg 
wrote:
 On 2015-09-02 22:25, w0rp wrote:

 Yeah, I would just call super.opEquals, like so.
I know that's the workaround, but the question is if it's a good implementation/behavior of opEquals.
Strictly speaking OO equality ought to yield three outcomes: yes, no, maybe. You can't really tell if an instance of Cat and and instance of Animal are equal based on features. It would be better to project an aspect of the instance and compare that instead ("equally powerful" etc).
Sep 08 2015
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 9/2/15 2:57 PM, Jacob Carlborg wrote:

 In this case the solution/workaround is to explicitly call
 super.opEquals, but that will miss some optimizations implemented in
 object.opEquals.
Those optimizations have already been exploited by the time you get to Foo.opEquals, so I wouldn't worry about that. However, the avoidance of casting would be a good goal. One of the things I don't like about the current == implementation for objects is it cannot take any advantage of type knowledge at the call site. -Steve
Sep 03 2015
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, 3 September 2015 at 13:05:49 UTC, Steven 
Schveighoffer wrote:
 On 9/2/15 2:57 PM, Jacob Carlborg wrote:

 In this case the solution/workaround is to explicitly call
 super.opEquals, but that will miss some optimizations 
 implemented in
 object.opEquals.
Those optimizations have already been exploited by the time you get to Foo.opEquals, so I wouldn't worry about that. However, the avoidance of casting would be a good goal. One of the things I don't like about the current == implementation for objects is it cannot take any advantage of type knowledge at the call site.
Every time I've tried to templatize the free function opEquals, I've run into compiler bugs, but we'll get there eventually. It looks like Kenji has a PR now to fix one of the issues: https://issues.dlang.org/show_bug.cgi?id=12537 So, I'll have to make another stab at it soon. - Jonathan M Davis
Sep 04 2015
parent reply "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Fri, Sep 04, 2015 at 07:10:25PM +0000, Jonathan M Davis via Digitalmars-d
wrote:
 On Thursday, 3 September 2015 at 13:05:49 UTC, Steven Schveighoffer wrote:
On 9/2/15 2:57 PM, Jacob Carlborg wrote:

In this case the solution/workaround is to explicitly call
super.opEquals, but that will miss some optimizations implemented in
object.opEquals.
Those optimizations have already been exploited by the time you get to Foo.opEquals, so I wouldn't worry about that. However, the avoidance of casting would be a good goal. One of the things I don't like about the current == implementation for objects is it cannot take any advantage of type knowledge at the call site.
Every time I've tried to templatize the free function opEquals, [...]
[...] Wait, wait, did I miss something? Since when was operator overloading allowed as free functions? Or is opEquals a special case? T -- If it tastes good, it's probably bad for you.
Sep 04 2015
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09/04/2015 09:21 PM, H. S. Teoh via Digitalmars-d wrote:
 Wait, wait, did I miss something? Since when was operator overloading
 allowed as free functions?
Since UFCS, but DMD does not implement it.
 Or is opEquals a special case?
Yup, quite special: http://dlang.org/operatoroverloading.html#equals
Sep 04 2015
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Friday, 4 September 2015 at 20:39:14 UTC, Timon Gehr wrote:
 On 09/04/2015 09:21 PM, H. S. Teoh via Digitalmars-d wrote:
 Wait, wait, did I miss something? Since when was operator 
 overloading
 allowed as free functions?
Since UFCS, but DMD does not implement it.
There is nothing in the spec about supporting operator overloading with free functions, so I don't know where you get the idea that it's even intended to be a feature. UFCS applies to functions which use the member function call syntax, and operators aren't used that way. There is no plan whatsoever to support operator overloading via free functions. - Jonathan M Davis
Sep 04 2015
next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2015-09-05 08:18, Jonathan M Davis wrote:

 There is nothing in the spec about supporting operator overloading with
 free functions, so I don't know where you get the idea that it's even
 intended to be a feature. UFCS applies to functions which use the member
 function call syntax, and operators aren't used that way. There is no
 plan whatsoever to support operator overloading via free functions.
Since "a == b" would be lowered to "a.opEquals(b)" one could argue that the compile would also try UFCS since it would do that if the code had been "a.opEquals(b)" from the beginning. -- /Jacob Carlborg
Sep 05 2015
parent "deadalnix" <deadalnix gmail.com> writes:
On Saturday, 5 September 2015 at 09:44:13 UTC, Jacob Carlborg 
wrote:
 On 2015-09-05 08:18, Jonathan M Davis wrote:

 There is nothing in the spec about supporting operator 
 overloading with
 free functions, so I don't know where you get the idea that 
 it's even
 intended to be a feature. UFCS applies to functions which use 
 the member
 function call syntax, and operators aren't used that way. 
 There is no
 plan whatsoever to support operator overloading via free 
 functions.
Since "a == b" would be lowered to "a.opEquals(b)" one could argue that the compile would also try UFCS since it would do that if the code had been "a.opEquals(b)" from the beginning.
The voice of reason.
Sep 09 2015
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09/05/2015 08:18 AM, Jonathan M Davis wrote:
 On Friday, 4 September 2015 at 20:39:14 UTC, Timon Gehr wrote:
 On 09/04/2015 09:21 PM, H. S. Teoh via Digitalmars-d wrote:
 Wait, wait, did I miss something? Since when was operator overloading
 allowed as free functions?
Since UFCS, but DMD does not implement it.
There is nothing in the spec about supporting operator overloading with free functions, so I don't know where you get the idea that it's even intended to be a feature. UFCS applies to functions which use the member function call syntax, and operators aren't used that way.
Specifying semantics via lowering is somewhat pointless if rewrites are not transitive.
 There is no plan whatsoever to support operator overloading via free functions.
 ...
Then specify operator overloading using __traits(getMember,...). (I consider this unwise though.)
Sep 07 2015
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, 7 September 2015 at 10:26:00 UTC, Timon Gehr wrote:
 On 09/05/2015 08:18 AM, Jonathan M Davis wrote:
 On Friday, 4 September 2015 at 20:39:14 UTC, Timon Gehr wrote:
 On 09/04/2015 09:21 PM, H. S. Teoh via Digitalmars-d wrote:
 Wait, wait, did I miss something? Since when was operator 
 overloading
 allowed as free functions?
Since UFCS, but DMD does not implement it.
There is nothing in the spec about supporting operator overloading with free functions, so I don't know where you get the idea that it's even intended to be a feature. UFCS applies to functions which use the member function call syntax, and operators aren't used that way.
Specifying semantics via lowering is somewhat pointless if rewrites are not transitive.
Specifying semantics via lowering makes the compiler simpler and the expected behavior easier to understand. Nothing about that requires that it transitively apply all syntactic sugar, and UFCS is simply syntactic sugar. Sure, it _could_ be implemented that way, but the only reason I see to do that is if we're specifically looking to support defining overloaded operators outside of the types that they apply to. I can't think of anything else that would be affected by it. Regardless, I honestly think that it would be a very bad technical decision to support defining overloaded operators outside of the type itself - _especially_ when you take into account operators that have defaults generated by the compiler (e.g. opEquals), since that would allow a third party to change what your code does by adding their own overloaded operator. And IIRC, Walter has explicitly stated that it's purposeful that you cannot define overloaded operators outside of the struct/class that they're for. So, anyone who wants it to be otherwise is going to have to convince him. - Jonathan M Davis
Sep 08 2015
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09/08/2015 06:49 PM, Jonathan M Davis wrote:
 On Monday, 7 September 2015 at 10:26:00 UTC, Timon Gehr wrote:
 On 09/05/2015 08:18 AM, Jonathan M Davis wrote:
 On Friday, 4 September 2015 at 20:39:14 UTC, Timon Gehr wrote:
 On 09/04/2015 09:21 PM, H. S. Teoh via Digitalmars-d wrote:
 Wait, wait, did I miss something? Since when was operator overloading
 allowed as free functions?
Since UFCS, but DMD does not implement it.
There is nothing in the spec about supporting operator overloading with free functions, so I don't know where you get the idea that it's even intended to be a feature. UFCS applies to functions which use the member function call syntax, and operators aren't used that way.
Specifying semantics via lowering is somewhat pointless if rewrites are not transitive.
Specifying semantics via lowering makes the compiler simpler
Not necessarily. The compiler can do lowering (even using internal syntax tree nodes that have no D syntax equivalent!) no matter how the behaviour is specified. It's just easier to come up with the required compiler code and to verify it if the specification does it this way.
 and the
 expected behavior easier to understand. Nothing about that  requires that
 it transitively apply all syntactic sugar, and UFCS is simply syntactic
 sugar.
All about that requires that it applies all rewrites transitively. It is the entire point that the lowered version is again plain D code. (Also, UFCS is not "simply syntactic sugar". There are special lookup rules. Operator overloading ought to be the simple syntactic sugar here, but it isn't, because built-in types don't define the respective operator overloading functions. They should.)
 Sure, it _could_ be implemented that way, but the only reason I
 see to do that is if we're specifically looking to support defining
 overloaded operators outside of the types that they apply to. I can't
 think of anything else that would be affected by it.
 ...
The compiler does not match the specification. I see no reason to change the specification here, but it would be easy.
 Regardless, I honestly think that it would be a very bad technical
 decision to support defining overloaded operators outside of the type
 itself - _especially_ when you take into account operators that have
 defaults generated by the compiler (e.g. opEquals), since that would
 allow a third party to change what your code does by adding their own
 overloaded operator.
Well, how? "Overloaded operators" are just specially named functions that support an additional call syntax. There are no strange issues with modularity that somehow only apply to overloaded operators. UFCS calls can never ignore methods of the type. It does not matter how they were generated. Was this your strongest point against having the compiler combine UFCS and operator call syntax in the straightforward fashion?
Sep 08 2015
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Tuesday, 8 September 2015 at 20:55:35 UTC, Timon Gehr wrote:
 On 09/08/2015 06:49 PM, Jonathan M Davis wrote:
 Sure, it _could_ be implemented that way, but the only reason I
 see to do that is if we're specifically looking to support 
 defining
 overloaded operators outside of the types that they apply to. 
 I can't
 think of anything else that would be affected by it.
 ...
The compiler does not match the specification. I see no reason to change the specification here, but it would be easy.
I don't know where you get this idea. The spec says _nothing_ about UFCS applying to overloaded operators or that UFCS would apply to lowered code in any way shape or form.
 Regardless, I honestly think that it would be a very bad 
 technical
 decision to support defining overloaded operators outside of 
 the type
 itself - _especially_ when you take into account operators 
 that have
 defaults generated by the compiler (e.g. opEquals), since that 
 would
 allow a third party to change what your code does by adding 
 their own
 overloaded operator.
Well, how? "Overloaded operators" are just specially named functions that support an additional call syntax. There are no strange issues with modularity that somehow only apply to overloaded operators. UFCS calls can never ignore methods of the type. It does not matter how they were generated. Was this your strongest point against having the compiler combine UFCS and operator call syntax in the straightforward fashion?
There would be no way to disambiguate overloaded operators if an operator were overloaded in multiple modules. foo + bar has no import paths involved in it at all. So, what would you do, write opBinary!"+"(foo, bar) instead? That's downright hideous, and it relies on you using the function call to emulate the operator correctly. For something like bar++, you'd be even more screwed, because it doesn't lower to a simple function call. UFCS is already enough of a problem on its own. It has some benefits, but it doesn't work when conflicts come into play, forcing you to just call the function normally, and its overload rules are such that it doesn't actually prevent hijacking in all cases (e.g. your code could suddenly change behavior, because you used UFCS with a type that then had a function with the same name and parameters added to it). And overloaded operators are closely tied to what a type does, whereas functions are far more diverse. So, there's a lot more to be gained with UFCS than with declaring overloaded operators separately from a type. I really don't see any reason why it would even make sense to declare operators separately from a type. So that you can declare multiple overloads of it for when you import different modules? That would just be plain confusing and incredibly error-prone. And if the problem is that you're dealing with someone else's type, then just declare a function to do what you want and be done with it. And do you really want people to be able to overload stray operators for stuff like strings and then write code that uses + and - and / or whatever on them? That would be ludicrous. It's one thing for people to do that with their own types. It's quite another to do that to built-in types or to someone else's types. What do you want next? To be able to declare constructors for someone else's type? That kind of stuff is part of the type, and allowing 3rd parties to declare it as well just makes it that much harder to figure out what's going on. At least when using UFCS, it's the exception that the function is on the type, so the overload rules don't usually shoot you in the foot (though they can), and you know to look elsewhere for the function just like you would with a function that was called normally. It's the complete opposite with operators. We have overloaded operators so that someone who is writing a user-defined type can make it act like a built-in type where appropriate. Allowing 3rd party code to declare overloaded operators for a type doesn't help with that at all. There is _nothing_ in the spec which supports UFCS having anything to do with overloaded operators and nothing to support overloading operators separately from a type. And I recall Walter Bright stating that it was on purpose that you can only overload operators on a type. So, even if the spec _could_ be interpreted to mean that you should be able to declare overloaded operators separately from the type that they apply to, that's _not_ its intention, and you're going to have to convince Walter if you want anything else. - Jonathan M Davis
Sep 08 2015
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09/09/2015 01:32 AM, Jonathan M Davis wrote:
 On Tuesday, 8 September 2015 at 20:55:35 UTC, Timon Gehr wrote:
 On 09/08/2015 06:49 PM, Jonathan M Davis wrote:
 Sure, it _could_ be implemented that way, but the only reason I
 see to do that is if we're specifically looking to support defining
 overloaded operators outside of the types that they apply to. I can't
 think of anything else that would be affected by it.
 ...
The compiler does not match the specification. I see no reason to change the specification here, but it would be easy.
I don't know where you get this idea.
Lowering transforms D code to other D code. See http://dlang.org/operatoroverloading.html : We have that e.g. "-e" gets rewritten to "e.opUnary!("-")()". "e.opUnary!("-")()" is D code. To specify the semantics implemented in DMD, the rewrite should be __traits(getMember,e,"opUnary")!("-")() (assuming that the parser is fixed so it can actually parse this, which would be useful in its own right.)
 Regardless, I honestly think that it would be a very bad technical
 decision to support defining overloaded operators outside of the type
 itself - _especially_ when you take into account operators that have
 defaults generated by the compiler (e.g. opEquals), since that would
 allow a third party to change what your code does by adding their own
 overloaded operator.
Well, how? "Overloaded operators" are just specially named functions that support an additional call syntax. There are no strange issues with modularity that somehow only apply to overloaded operators. ...
There would be no way to disambiguate overloaded operators if an operator were overloaded in multiple modules. ... ...
Yes, there would be. Just use aliases. (Also, overloaded operators are not actually that likely to cause conflicts, because they are best written to match a suitably constrained set of types, just like other functions.)
 UFCS is already enough of a problem on its own. It has some benefits,
 but it doesn't work when conflicts come into play, forcing you to just
 call the function normally,
No, it does not force you to do that.
 and its overload rules are such that it
 doesn't actually prevent hijacking in all cases (e.g. your code could
 suddenly change behavior, because you used UFCS with a type that then
 had a function with the same name and parameters added to it).
I think that's universally understood, but I don't see how this relates to the issue at hand.
 And
 overloaded operators are closely tied to what a type does, whereas
 functions are far more diverse.  So, there's a lot more to be gained with
 UFCS than with declaring overloaded operators separately from a type.
UFCS and this ad-hoc terminology do not describe separate things.
 So that you can declare multiple
 overloads of it for when you import different modules? That would just
 be plain confusing and incredibly error-prone.
(Straw man.)
 And if the problem is
 that you're dealing with someone else's type, then just declare a
 function to do what you want and be done with it.
I agree with this part, overloaded operators are just such functions.
 And do you really want
 people to be able to overload stray operators for stuff like strings and
 then write code that uses + and - and / or whatever on them? That would
 be ludicrous. It's one thing for people to do that with their own types.
 It's quite another to do that to built-in types or to someone else's
 types.
This is not distinct from being able to declare functions operating on built-in types or someone else's types that have other unsuitable names. Also, in case you missed it, I also briefly promoted the idea that built-in types should define the operator overloading functions in order to make treatment of operators uniform (as far as the specification is concerned, the compiler could just rewrite the special members to whatever internal representation it uses now for the built-in types). In particular, "+", "-" and "/" can't be overloaded externally on strings in such a setting.
 What do you want next?
(That's a nice way of introducing a straw man.)
 To be able to declare constructors for someone else's type?
That's not even close to being similar.
 That kind of stuff
:o) AFAICT, they were just clustered together randomly in order to artificially boost the argument.
 is part of the type, and
 allowing 3rd parties to declare it as well just makes it that much
 harder to figure out what's going on. At least when using UFCS, it's the
 exception that the function is on the type, so the overload rules don't
 usually shoot you in the foot (though they can), and you know to look
 elsewhere for the function just like you would with a function that was
 called normally. It's the complete opposite with operators.
 ...
I don't see where you get this idea. It should be noted that operator overloading isn't even that common and basically all legitimate usages are obvious. (They are those cases where a operator is the most natural name for a function.)
 We have overloaded operators so that someone who is writing a
 user-defined type can make it act like a built-in type where
 appropriate. Allowing 3rd party code to declare overloaded operators for
 a type doesn't help with that at all.
 ...
On 09/09/2015 01:32 AM, Jonathan M Davis wrote: (moved from above)
 I really don't see any reason why it would even make sense to declare
 operators separately from a type.
One reason is that single dispatch can be awkward. A textbook example would be: you have a set of special matrix types (dense, sparse, diagonal, triangular, column-first, row-first, etc.), and now want to implement multiplication efficiently for all pairs of types via static dispatch. Why should I have to jump through hoops just to be able to use suitable function names that are associated with the wanted call syntax? Another reason is that it is annoying that overloaded operators choose the calling convention for you, asymmetrically only for the first argument. It certainly shouldn't be hard to produce more of those examples. Non-orthogonal features tend to cause subtle (and less subtle) pain points. It would be better already if UFCS only worked for overloaded operators if the type and the operator where defined in the same module, but I really don't see the point of adding arbitrary limitations to the language. There might be legitimate use cases.
 There is _nothing_ in the spec which supports UFCS having anything to do
 with overloaded operators
On 09/09/2015 01:32 AM, Jonathan M Davis wrote: (moved from the beginning of the post)
 The spec says _nothing_ about UFCS
 applying to overloaded operators or that UFCS would apply to lowered
 code in any way shape or form.
 ...
Please stop palindrome-posting. (This is a general tendency in your posts, I wouldn't have brought it up otherwise: they (approximately) start with topic A, go to topic B, then C, return to B and then finish with A. Together with the missing emphasis on conciseness, it can get a little bit tiring at times.) If you read http://dlang.org/function.html#pseudo-member carefully, nowhere is it explicitly mentioned that it works with built-in types or structs, or with classes named "NoUFCS". Would you also claim that the intention of the specification to support UFCS for those types is questionable? Your argument seems to apply equally well to those cases.
 and nothing to support overloading operators
 separately from a type. And I recall Walter Bright stating that it was
 on purpose that you can only overload operators on a type. So, even if
 the spec _could_ be interpreted to mean that you should be able to
 declare overloaded operators separately from the type that they apply
 to, that's _not_ its intention, and you're going to have to convince
 Walter if you want anything else.
...
I might do that eventually, but there are more important things to consider at this point.
Sep 09 2015
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 09/09/2015 09:20 PM, Timon Gehr wrote:
 On 09/09/2015 01:32 AM, Jonathan M Davis wrote: (moved from above)
 I really don't see any reason why it would even make sense to declare
 operators separately from a type.
One reason is that single dispatch can be awkward. A textbook example would be: ...
I just noticed that I missed to concretely mention one obvious use case: Overloading mutating operators on reference types with ("logical") value semantics, possibly in combination with hash consing. E.g. classObject++, or classObject+=x.
Sep 17 2015
prev sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Friday, 4 September 2015 at 19:25:35 UTC, H. S. Teoh wrote:
 Wait, wait, did I miss something? Since when was operator 
 overloading allowed as free functions? Or is opEquals a special 
 case?
Clearly, you haven't read TDPL recently enough. ;) There is a free function, opEquals, in object.d which gets called for classes, and _it_ is what == gets translated to for classes, and it calls the member function version of opEquals on classes: https://github.com/D-Programming-Language/druntime/blob/master/src/object.d#L143 This allows us to avoid a number of fun bugs with opEquals that you get in languages like Java and makes it completely unnecessary to do stuff like check whether the argument to opEquals is null. Timon gave the link to the explanation in the spec: http://dlang.org/operatoroverloading.html#equals but TDPL also talks about it. But as part of the fix for https://issues.dlang.org/show_bug.cgi?id=9769 we need to templatize the free function opEquals so that it doesn't require Object. Then opEquals on a class will take whatever the most base class in your class hierarchy is that has opEquals rather than Object, and we can actually deprecate opEquals on Object (though that may require some additional compiler help rather than simply deprecating it; I'm not sure). Regardless, templatizing the free function version of opEquals is the first step towards that. - Jonathan M Davis
Sep 04 2015
parent reply Jacob Carlborg <doob me.com> writes:
On 2015-09-05 08:26, Jonathan M Davis wrote:

 Clearly, you haven't read TDPL recently enough. ;)

 There is a free function, opEquals, in object.d which gets called for
 classes, and _it_ is what == gets translated to for classes, and it
 calls the member function version of opEquals on classes:

 https://github.com/D-Programming-Language/druntime/blob/master/src/object.d#L143


 This allows us to avoid a number of fun bugs with opEquals that you get
 in languages like Java and makes it completely unnecessary to do stuff
 like check whether the argument to opEquals is null. Timon gave the link
 to the explanation in the spec:
Bu you don't see my example as a problem? -- /Jacob Carlborg
Sep 05 2015
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Saturday, 5 September 2015 at 09:45:36 UTC, Jacob Carlborg 
wrote:
 On 2015-09-05 08:26, Jonathan M Davis wrote:

 Clearly, you haven't read TDPL recently enough. ;)

 There is a free function, opEquals, in object.d which gets 
 called for
 classes, and _it_ is what == gets translated to for classes, 
 and it
 calls the member function version of opEquals on classes:

 https://github.com/D-Programming-Language/druntime/blob/master/src/object.d#L143


 This allows us to avoid a number of fun bugs with opEquals 
 that you get
 in languages like Java and makes it completely unnecessary to 
 do stuff
 like check whether the argument to opEquals is null. Timon 
 gave the link
 to the explanation in the spec:
Bu you don't see my example as a problem?
Well, it might be a bit annoying, but it's simply a matter of adjusting your code to call opEquals explicitly when trying to call the base version, whereas without the free function opEquals, you have subtle correctness problems. For instance, if you have base == derived and derived == base, you'll get the same result for both for D, whereas the equivalent Java or C# could would likely not, because the free function opEquals checks both lhs.opEquals(rhs) and rhs.OpEquals(lhs) whether you did base == derived or derived == base. So, while what we have is by no means perfect, I think that it is an improvement over what Java and C# did. - Jonathan M Davis
Sep 08 2015
parent Jacob Carlborg <doob me.com> writes:
On 2015-09-08 18:40, Jonathan M Davis wrote:

 Well, it might be a bit annoying, but it's simply a matter of adjusting
 your code to call opEquals explicitly when trying to call the base
 version
Given that fact that I found this problem in std.xml.Document shows either that this is not so easy to remember or that std.xml is basically never used and it doesn't have a test for this. That it doesn't have a test is true regardless.
 ,whereas without the free function opEquals, you have subtle
 correctness problems. For instance, if you have base == derived and
 derived == base, you'll get the same result for both for D, whereas the
 equivalent Java or C# could would likely not, because the free function
 opEquals checks both lhs.opEquals(rhs) and rhs.OpEquals(lhs) whether you
 did base == derived or derived == base.

 So, while what we have is by no means perfect, I think that it is an
 improvement over what Java and C# did.
It should be possible for the compiler to see that it's a super call and generate a call to opSuperEquals (or similar) instead, which would make a non-virtual call when calling opEquals on the instance. -- /Jacob Carlborg
Sep 08 2015