digitalmars.D - Behavior of opEquals
- Jacob Carlborg (44/44) Sep 02 2015 I encountered a problem in the implementation of
- w0rp (30/73) Sep 02 2015 Yeah, I would just call super.opEquals, like so.
- Jacob Carlborg (5/6) Sep 02 2015 I know that's the workaround, but the question is if it's a good
- "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= (8/12) Sep 08 2015 Strictly speaking OO equality ought to yield three outcomes: yes,
- Steven Schveighoffer (7/10) Sep 03 2015 Those optimizations have already been exploited by the time you get to
- Jonathan M Davis (8/19) Sep 04 2015 Every time I've tried to templatize the free function opEquals,
- H. S. Teoh via Digitalmars-d (7/22) Sep 04 2015 [...]
- Timon Gehr (3/6) Sep 04 2015 Yup, quite special: http://dlang.org/operatoroverloading.html#equals
- Jonathan M Davis (8/14) Sep 04 2015 There is nothing in the spec about supporting operator
- Jacob Carlborg (6/11) Sep 05 2015 Since "a == b" would be lowered to "a.opEquals(b)" one could argue that
- deadalnix (3/17) Sep 09 2015 The voice of reason.
- Timon Gehr (5/18) Sep 07 2015 Specifying semantics via lowering is somewhat pointless if rewrites are
- Jonathan M Davis (20/39) Sep 08 2015 Specifying semantics via lowering makes the compiler simpler and
- Timon Gehr (19/52) Sep 08 2015 Not necessarily. The compiler can do lowering (even using internal
- Jonathan M Davis (55/83) Sep 08 2015 I don't know where you get this idea. The spec says _nothing_
- Timon Gehr (65/144) Sep 09 2015 Lowering transforms D code to other D code.
- Timon Gehr (5/10) Sep 17 2015 I just noticed that I missed to concretely mention one obvious use case:
- Jonathan M Davis (23/26) Sep 04 2015 Clearly, you haven't read TDPL recently enough. ;)
- Jacob Carlborg (4/13) Sep 05 2015 Bu you don't see my example as a problem?
- Jonathan M Davis (14/34) Sep 08 2015 Well, it might be a bit annoying, but it's simply a matter of
- Jacob Carlborg (10/21) Sep 08 2015 Given that fact that I found this problem in std.xml.Document shows
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
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
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
On Thursday, 3 September 2015 at 06:37:20 UTC, Jacob Carlborg wrote:On 2015-09-02 22:25, w0rp wrote: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).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.
Sep 08 2015
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
On Thursday, 3 September 2015 at 13:05:49 UTC, Steven Schveighoffer wrote:On 9/2/15 2:57 PM, Jacob Carlborg wrote: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 DavisIn 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.
Sep 04 2015
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:[...] 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.On 9/2/15 2:57 PM, Jacob Carlborg wrote:Every time I've tried to templatize the free function opEquals, [...]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.
Sep 04 2015
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
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: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 DavisWait, wait, did I miss something? Since when was operator overloading allowed as free functions?Since UFCS, but DMD does not implement it.
Sep 04 2015
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
On Saturday, 5 September 2015 at 09:44:13 UTC, Jacob Carlborg wrote:On 2015-09-05 08:18, Jonathan M Davis wrote:The voice of reason.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.
Sep 09 2015
On 09/05/2015 08:18 AM, Jonathan M Davis wrote:On Friday, 4 September 2015 at 20:39:14 UTC, Timon Gehr wrote:Specifying semantics via lowering is somewhat pointless if rewrites are not transitive.On 09/04/2015 09:21 PM, H. S. Teoh via Digitalmars-d 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.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 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
On Monday, 7 September 2015 at 10:26:00 UTC, Timon Gehr wrote:On 09/05/2015 08:18 AM, Jonathan M Davis wrote: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 DavisOn Friday, 4 September 2015 at 20:39:14 UTC, Timon Gehr wrote:Specifying semantics via lowering is somewhat pointless if rewrites are not transitive.On 09/04/2015 09:21 PM, H. S. Teoh via Digitalmars-d 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.Wait, wait, did I miss something? Since when was operator overloading allowed as free functions?Since UFCS, but DMD does not implement it.
Sep 08 2015
On 09/08/2015 06:49 PM, Jonathan M Davis wrote:On Monday, 7 September 2015 at 10:26:00 UTC, Timon Gehr wrote: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.On 09/05/2015 08:18 AM, Jonathan M Davis wrote:Specifying semantics via lowering makes the compiler simplerOn Friday, 4 September 2015 at 20:39:14 UTC, Timon Gehr wrote:Specifying semantics via lowering is somewhat pointless if rewrites are not transitive.On 09/04/2015 09:21 PM, H. S. Teoh via Digitalmars-d 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.Wait, wait, did I miss something? Since when was operator overloading allowed as free functions?Since UFCS, but DMD does not implement it.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
On Tuesday, 8 September 2015 at 20:55:35 UTC, Timon Gehr wrote:On 09/08/2015 06:49 PM, Jonathan M Davis wrote: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.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.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 DavisRegardless, 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
On 09/09/2015 01:32 AM, Jonathan M Davis wrote:On Tuesday, 8 September 2015 at 20:55:35 UTC, Timon Gehr wrote: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.)On 09/08/2015 06:49 PM, Jonathan M Davis wrote:I don't know where you get this idea.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.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.)There would be no way to disambiguate overloaded operators if an operator were overloaded in multiple modules. ... ...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 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 operatorsOn 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
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 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.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: ...
Sep 17 2015
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
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
On Saturday, 5 September 2015 at 09:45:36 UTC, Jacob Carlborg wrote:On 2015-09-05 08:26, 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, 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 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 - Jonathan M DavisClearly, 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?
Sep 08 2015
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 versionGiven 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 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 anIt 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