Bug 3659 – Too much exegesis on opEquals

Status
RESOLVED
Resolution
FIXED
Severity
blocker
Priority
P2
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2009-12-30T09:14:00Z
Last change time
2011-09-29T23:47:40Z
Keywords
patch, rejects-valid, spec
Assigned to
nobody
Creator
andrei
Blocks
5281, 5889

Comments

Comment #0 by andrei — 2009-12-30T09:14:06Z
struct A { bool opEquals(int x) { return false; } } Compiler says: Error: function test.A.opEquals type signature should be const bool(ref const(A)) not bool(int x) The compiler should accept any signature for opEquals. All it needs to do is rewrite a == b into a.opEquals(b) or b.opEquals(a). For classes I'm doubtful that opEquals needs to be const.
Comment #1 by webby — 2009-12-30T09:42:14Z
Same as #3607 ?
Comment #2 by schveiguy — 2009-12-31T05:51:24Z
Here is the issue. The compiler now is required to generate an opEquals which calls x.m == y.m on all members m of the struct (this was to fix bug 3433). However, in order for this opEquals to handle const and immutable versions of the struct, the opEquals generated needs to be const. Given that, if member m is another struct with a *user defined* opEquals, that opEquals must *also* be const. The current rule is too strict, I agree. For instance, you should not require that the argument be ref const if the argument type can be implicitly cast from const to mutable (i.e. a pure value type), but if the argument is ref, it *should* be const, and the opEquals function itself *should* be const. Otherwise, you could be changing stuff just by doing a comparison. What is the use case for an opEquals not being const? Another alternative is to allow any signature for opEquals on type T, but then any type U which has a member of type T will be illegal to compare unless type U also defines opEquals.
Comment #3 by andrei — 2009-12-31T07:37:33Z
(In reply to comment #2) > Here is the issue. The compiler now is required to generate an opEquals which > calls x.m == y.m on all members m of the struct (this was to fix bug 3433). > However, in order for this opEquals to handle const and immutable versions of > the struct, the opEquals generated needs to be const. Given that, if member m > is another struct with a *user defined* opEquals, that opEquals must *also* be > const. I see, thanks for explaining. > The current rule is too strict, I agree. For instance, you should not require > that the argument be ref const if the argument type can be implicitly cast from > const to mutable (i.e. a pure value type), but if the argument is ref, it > *should* be const, and the opEquals function itself *should* be const. > Otherwise, you could be changing stuff just by doing a comparison. > > What is the use case for an opEquals not being const? I'm thinking of our current stance on const: if you don't care about const, don't use it and for the most part it won't intrude on you. For example, string's use of immutability is fairly confined. opEquals is a stark deviation from the stance above. It *will* intrude. The classic example is this: class Widget { bool opEquals(Widget); } Compiling this issues: Warning: object.Object.opEquals(Object o) is hidden by Widget It only gets downhill from there: struct Widget { bool opEquals(Widget) { return true; } } This time it's an error: Error: function test.Widget.opEquals type signature should be const bool(ref const(Widget)) not bool(ref Widget) So you simply can't "not care" about const. But then it's all getting viral because cons is viral. Consider the user grudgingly agrees to add const, and then... class Widget { private Gadget g; bool opEquals(const Widget rhs) { return compatibleGadgets(g, rhs.g); } } But now it's still not fine because compatibleGadgets is also written without caring about const. It's all a mess. Now consider that the user capitulates and decides to use const wherever applicable. Things will still not work in certain cases. For example if opEquals must compare members that are lazily computed, you can't make it compile. Cheating by casting away const will mess up a variety of assumptions. As an aside, I know what it takes to define lazily computed state to work with const, but Walter is at the bottom of a 5000 TeV potential hole that spells like "this is like C++ mutable and C++ mutable is incorrect, therefore I will not process any information henceforth". So I am unable to even start explaining that to him. Besides, assuming Walter is convinced of the correctness of the feature, it's unclear whether it will pull its weight. It will complicate the language, and the benefits, while there, are rather subtle. So I don't know what to do.
Comment #4 by schveiguy — 2009-12-31T09:45:21Z
(In reply to comment #3) > I'm thinking of our current stance on const: if you don't care about const, > don't use it and for the most part it won't intrude on you. For example, > string's use of immutability is fairly confined. Well, that wasn't the case not too long ago. For example, all std.string functions used strings as args, making doing simple things like searching for substrings in mutable strings impossible. I think if we work harder, we may find some acceptable middle ground. > opEquals is a stark deviation from the stance above. It *will* intrude. The > classic example is this: > > class Widget { > bool opEquals(Widget); > } Structs and classes are different stories. A class is always passed by reference, so I think it must always be const as an argument to an opEquals. This goes with my assertion above. The counter-argument to having opEquals not be const on Object is then you cannot compare 2 const objects without defining separate functions. > > So you simply can't "not care" about const. But then it's all getting viral > because cons is viral. Consider the user grudgingly agrees to add const, and > then... > > class Widget { > private Gadget g; > bool opEquals(const Widget rhs) { > return compatibleGadgets(g, rhs.g); > } > } > > But now it's still not fine because compatibleGadgets is also written without > caring about const. It's all a mess. There is no way around it. Const is viral, and in order to guarantee what it does, it has to be. The proposed inout helps in regards to not making things const when they don't have to be, but at some point, the compiler has to either give in (i.e. C++ mutable) or put its foot down. Have you considered the other alternative that you didn't copy in reply: "Another alternative is to allow any signature for opEquals on type T, but then any type U which has a member of type T will be illegal to compare unless type U also defines opEquals." I think this is a reasonable compromise, and doesn't require const signatures. The thing I don't like about the current solution is it requires a certain signature even if you *never* include it as a member of another aggregate, just in case the compiler has to write an opEquals around it. > Now consider that the user capitulates and decides to use const wherever > applicable. Things will still not work in certain cases. For example if > opEquals must compare members that are lazily computed, you can't make it > compile. Cheating by casting away const will mess up a variety of assumptions. > > As an aside, I know what it takes to define lazily computed state to work with > const, but Walter is at the bottom of a 5000 TeV potential hole that spells > like "this is like C++ mutable and C++ mutable is incorrect, therefore I will > not process any information henceforth". So I am unable to even start > explaining that to him. Besides, assuming Walter is convinced of the > correctness of the feature, it's unclear whether it will pull its weight. It > will complicate the language, and the benefits, while there, are rather subtle. This is logical const all over again :) Note that I think this is possibly the only use case for opEquals not being const. To compare 2 items and have them visibly change is against any expectation I've ever had. But this doesn't mean we need to remove const from the signature, it just means we need to find a way to make logical const work. I remember arguing this with Janice a while back, Walter never participated, and you were MIA. Is there a possible library solution? i.e. something like: private mutable!(Gadget) g; Where g is always mutable, no matter what it's container's mutability (i.e. contain the dangerous const casts to a library type). I'm sure there are some template wizards out there who can make this happen, especially with opDispatch now :)
Comment #5 by tomeksowi — 2010-01-20T11:27:48Z
Another quirk caused by forcing const in opEquals is bug 3729.
Comment #6 by clugdbug — 2010-12-06T00:38:51Z
Raising severity, as I think this is one of the crucial unresolved issues in the const system.
Comment #7 by yebblies — 2011-06-12T22:44:02Z
*** Issue 3607 has been marked as a duplicate of this issue. ***
Comment #8 by yebblies — 2011-06-12T22:44:10Z
*** Issue 3729 has been marked as a duplicate of this issue. ***
Comment #9 by yebblies — 2011-06-15T23:30:06Z
*** Issue 6163 has been marked as a duplicate of this issue. ***
Comment #10 by k.hara.pg — 2011-09-09T04:07:10Z
Comment #11 by bugzilla — 2011-09-29T23:47:40Z