digitalmars.D - Why struct opEquals must be const?
- "Nick Sabalausky" <a a.a> Oct 16 2010
- "Denis Koroskin" <2korden gmail.com> Oct 17 2010
- Jonathan M Davis <jmdavisProg gmx.com> Oct 17 2010
- so <so so.do> Oct 18 2010
- "Steven Schveighoffer" <schveiguy yahoo.com> Oct 18 2010
- Pelle <pelle.mansson gmail.com> Oct 18 2010
- Pelle <pelle.mansson gmail.com> Oct 18 2010
- Pelle <pelle.mansson gmail.com> Oct 18 2010
- Rainer Deyke <rainerd eldwood.com> Oct 18 2010
- Rainer Deyke <rainerd eldwood.com> Oct 18 2010
- "Steven Schveighoffer" <schveiguy yahoo.com> Oct 18 2010
- "Steven Schveighoffer" <schveiguy yahoo.com> Oct 18 2010
- "Steven Schveighoffer" <schveiguy yahoo.com> Oct 18 2010
- "Steven Schveighoffer" <schveiguy yahoo.com> Oct 19 2010
Is there a technical reason why the l- and r- values for opEquals must be const? If the restriction is purely for the intuitive notion that there's no heisenstructs, then I have an example I think might be worth consideration: lazy caching. If comparison isn't always needed and requires a potentially expensive computation that isn't likely needed otherwise (for example, a wrapper for string that does case-insensitive comparisons, if it's used in a situation that does more assigning/slicing/etc than comparing), then it may make sense to wait until an opEquals is called, and then compute the information and cache it. But that requires mutating state and so can't be done in a struct opEquals. 'Course, if there is a technical reason for the restriction, then all this is moot.
Oct 16 2010
On Sun, 17 Oct 2010 10:57:02 +0400, Nick Sabalausky <a a.a> wrote:Is there a technical reason why the l- and r- values for opEquals must be const? If the restriction is purely for the intuitive notion that there's no heisenstructs, then I have an example I think might be worth consideration: lazy caching. If comparison isn't always needed and requires a potentially expensive computation that isn't likely needed otherwise (for example, a wrapper for string that does case-insensitive comparisons, if it's used in a situation that does more assigning/slicing/etc than comparing), then it may make sense to wait until an opEquals is called, and then compute the information and cache it. But that requires mutating state and so can't be done in a struct opEquals. 'Course, if there is a technical reason for the restriction, then all this is moot.
I don't think there any. None of the Object's methods are const (e.g. opEquals, toHash etc), why would struct need to be const?
Oct 17 2010
On Sunday 17 October 2010 00:02:00 Denis Koroskin wrote:On Sun, 17 Oct 2010 10:57:02 +0400, Nick Sabalausky <a a.a> wrote:Is there a technical reason why the l- and r- values for opEquals must be const? If the restriction is purely for the intuitive notion that there's no heisenstructs, then I have an example I think might be worth consideration: lazy caching. If comparison isn't always needed and requires a potentially expensive computation that isn't likely needed otherwise (for example, a wrapper for string that does case-insensitive comparisons, if it's used in a situation that does more assigning/slicing/etc than comparing), then it may make sense to wait until an opEquals is called, and then compute the information and cache it. But that requires mutating state and so can't be done in a struct opEquals. 'Course, if there is a technical reason for the restriction, then all this is moot.
I don't think there any. None of the Object's methods are const (e.g. opEquals, toHash etc), why would struct need to be const?
They're supposed to be const. There's a long-standing bug over the fact that they aren't const: http://d.puremagic.com/issues/show_bug.cgi?id=1824 The fact that they aren't const is a big problem. opEquals(), toHash(), etc. need to work on const and immutable objects. If they don't, const and immutable quickly start becoming useless. And really, for const and immutable to work well, we really should be trying to make _more_ functions const, not fewer. Now, I don't see any reason why we couldn't have a const and non-const version of functions like opEquals() and toHash() and have non-const objects use the non-const versions and take advantage of whatever caching facilities they might provide, but we _need_ to have const versions of these functions for const and immutable to be properly useable. - Jonathan M Davis
Oct 17 2010
If you want to invoke a generic function that calls some methods of your input, most of the time you need to be sure these methods are "const-correct", especially when your input is const. It is both a good and bad thing. On Sun, 17 Oct 2010 09:57:02 +0300, Nick Sabalausky <a a.a> wrote:Is there a technical reason why the l- and r- values for opEquals must be const? If the restriction is purely for the intuitive notion that there's no heisenstructs, then I have an example I think might be worth consideration: lazy caching. If comparison isn't always needed and requires a potentially expensive computation that isn't likely needed otherwise (for example, a wrapper for string that does case-insensitive comparisons, if it's used in a situation that does more assigning/slicing/etc than comparing), then it may make sense to wait until an opEquals is called, and then compute the information and cache it. But that requires mutating state and so can't be done in a struct opEquals. 'Course, if there is a technical reason for the restriction, then all this is moot.
-- Using Opera's revolutionary e-mail client: http://www.opera.com/mail/
Oct 18 2010
On Sun, 17 Oct 2010 02:57:02 -0400, Nick Sabalausky <a a.a> wrote:Is there a technical reason why the l- and r- values for opEquals must be const? If the restriction is purely for the intuitive notion that there's no heisenstructs, then I have an example I think might be worth consideration: lazy caching. If comparison isn't always needed and requires a potentially expensive computation that isn't likely needed otherwise (for example, a wrapper for string that does case-insensitive comparisons, if it's used in a situation that does more assigning/slicing/etc than comparing), then it may make sense to wait until an opEquals is called, and then compute the information and cache it. But that requires mutating state and so can't be done in a struct opEquals. 'Course, if there is a technical reason for the restriction, then all this is moot.
http://d.puremagic.com/issues/show_bug.cgi?id=3659 -Steve
Oct 18 2010
On 10/18/2010 02:41 PM, Steven Schveighoffer wrote:On Sun, 17 Oct 2010 02:57:02 -0400, Nick Sabalausky <a a.a> wrote:Is there a technical reason why the l- and r- values for opEquals must be const? If the restriction is purely for the intuitive notion that there's no heisenstructs, then I have an example I think might be worth consideration: lazy caching. If comparison isn't always needed and requires a potentially expensive computation that isn't likely needed otherwise (for example, a wrapper for string that does case-insensitive comparisons, if it's used in a situation that does more assigning/slicing/etc than comparing), then it may make sense to wait until an opEquals is called, and then compute the information and cache it. But that requires mutating state and so can't be done in a struct opEquals. 'Course, if there is a technical reason for the restriction, then all this is moot.
http://d.puremagic.com/issues/show_bug.cgi?id=3659 -Steve
Shouldn't the compiler make the opEquals non-const if one of the struct members requires mutability for equality testing? Just a thought.
Oct 18 2010
On 10/18/2010 05:07 PM, Steven Schveighoffer wrote:On Mon, 18 Oct 2010 10:49:25 -0400, Pelle <pelle.mansson gmail.com> wrote:Shouldn't the compiler make the opEquals non-const if one of the struct members requires mutability for equality testing? Just a thought.
IMO, opEquals should allow non-const, but only if the type is a value type (no references). This means opEquals on a class should always be const. Allowing opEquals to modify the original object violates the expectation of opEquals -- you don't expect comparison to change the objects being compared. But logically, opEquals shouldn't require any specific signature -- it's just another function. What case were you thinking of for requiring mutablity for equality testing? -Steve
I was thinking along the lines of not enforcing unexpected restrictions on programmers because we couldn't think of a use case. This isn't an enabler. Today, the check isn't very intelligent, which makes immutable structs unusable.
Oct 18 2010
On 10/18/2010 05:55 PM, Steven Schveighoffer wrote:On Mon, 18 Oct 2010 11:19:54 -0400, Pelle <pelle.mansson gmail.com> wrote:On 10/18/2010 05:07 PM, Steven Schveighoffer wrote:
What case were you thinking of for requiring mutablity for equality testing? -Steve
I was thinking along the lines of not enforcing unexpected restrictions on programmers because we couldn't think of a use case. This isn't an enabler.
But D also has a good goal of not enabling bad design. Even if through convention. I personally don't see how opEquals can be forced into a particular signature (the current rules are too strict), but a philosophy that opEquals should refrain from changing the objects in question is a good one to have.
I agree with this philosophy :-) I do not think it should be compiler enforced, however.Today, the check isn't very intelligent, which makes immutable structs unusable.
Absolutely, hence the bug report. -Steve
Oct 18 2010
On 10/18/2010 09:07, Steven Schveighoffer wrote:What case were you thinking of for requiring mutablity for equality testing?
What about caching? class C { private int hashValue = -1; // Mutators reset hashValue to -1. int getHash() { if (this.hashValue == -1) { this.hashValue = longExpensiveCalculation(); } return this.hashValue(); } bool opEquals(C other) { if (this.getHash() == other.getHash()) { return slowElementwiseCompare(this, other); } else { return false; } } } -- Rainer Deyke - rainerd eldwood.com
Oct 18 2010
On 10/18/2010 13:51, Steven Schveighoffer wrote:On Mon, 18 Oct 2010 15:43:29 -0400, Rainer Deyke <rainerd eldwood.com> wrote:On 10/18/2010 09:07, Steven Schveighoffer wrote:What case were you thinking of for requiring mutablity for equality testing?
What about caching?
The object's state is still changing.
Yes, that's my point. D doesn't have "logical const" like C++, so not all logically const operations can be declared const. opEquals is always logically const, but it cannot always be physically const.If you think it's worth doing, you can always cast. Despite the label of 'undefined behavior', I believe the compiler will behave fine if you do that (it can't really optimize out const calls).
Not safe. The set of const objects includes immutable objects, and immutable objects can be accessed from multiple threads at the same time, but caching is not (by default) thread-safe. -- Rainer Deyke - rainerd eldwood.com
Oct 18 2010
On Mon, 18 Oct 2010 10:49:25 -0400, Pelle <pelle.mansson gmail.com> wrote:On 10/18/2010 02:41 PM, Steven Schveighoffer wrote:On Sun, 17 Oct 2010 02:57:02 -0400, Nick Sabalausky <a a.a> wrote:Is there a technical reason why the l- and r- values for opEquals must be const? If the restriction is purely for the intuitive notion that there's no heisenstructs, then I have an example I think might be worth consideration: lazy caching. If comparison isn't always needed and requires a potentially expensive computation that isn't likely needed otherwise (for example, a wrapper for string that does case-insensitive comparisons, if it's used in a situation that does more assigning/slicing/etc than comparing), then it may make sense to wait until an opEquals is called, and then compute the information and cache it. But that requires mutating state and so can't be done in a struct opEquals. 'Course, if there is a technical reason for the restriction, then all this is moot.
http://d.puremagic.com/issues/show_bug.cgi?id=3659 -Steve
Shouldn't the compiler make the opEquals non-const if one of the struct members requires mutability for equality testing? Just a thought.
IMO, opEquals should allow non-const, but only if the type is a value type (no references). This means opEquals on a class should always be const. Allowing opEquals to modify the original object violates the expectation of opEquals -- you don't expect comparison to change the objects being compared. But logically, opEquals shouldn't require any specific signature -- it's just another function. What case were you thinking of for requiring mutablity for equality testing? -Steve
Oct 18 2010
On Mon, 18 Oct 2010 11:19:54 -0400, Pelle <pelle.mansson gmail.com> wrote:On 10/18/2010 05:07 PM, Steven Schveighoffer wrote:
What case were you thinking of for requiring mutablity for equality testing? -Steve
I was thinking along the lines of not enforcing unexpected restrictions on programmers because we couldn't think of a use case. This isn't an enabler.
But D also has a good goal of not enabling bad design. Even if through convention. I personally don't see how opEquals can be forced into a particular signature (the current rules are too strict), but a philosophy that opEquals should refrain from changing the objects in question is a good one to have.Today, the check isn't very intelligent, which makes immutable structs unusable.
Absolutely, hence the bug report. -Steve
Oct 18 2010
On Mon, 18 Oct 2010 15:43:29 -0400, Rainer Deyke <rainerd eldwood.com> wrote:On 10/18/2010 09:07, Steven Schveighoffer wrote:What case were you thinking of for requiring mutablity for equality testing?
What about caching? class C { private int hashValue = -1; // Mutators reset hashValue to -1. int getHash() { if (this.hashValue == -1) { this.hashValue = longExpensiveCalculation(); } return this.hashValue(); } bool opEquals(C other) { if (this.getHash() == other.getHash()) { return slowElementwiseCompare(this, other); } else { return false; } } }
The object's state is still changing. If you don't consider hashValue to be part of the object's state, then that's what we call 'logical const' (implemented in C++ via the mutable keyword). Only certain members get such privileged status (such as the monitor object). If you think it's worth doing, you can always cast. Despite the label of 'undefined behavior', I believe the compiler will behave fine if you do that (it can't really optimize out const calls). -Steve
Oct 18 2010
On Mon, 18 Oct 2010 17:31:31 -0400, Rainer Deyke <rainerd eldwood.com> wrote:On 10/18/2010 13:51, Steven Schveighoffer wrote:On Mon, 18 Oct 2010 15:43:29 -0400, Rainer Deyke <rainerd eldwood.com> wrote:On 10/18/2010 09:07, Steven Schveighoffer wrote:What case were you thinking of for requiring mutablity for equality testing?
What about caching?
The object's state is still changing.
Yes, that's my point. D doesn't have "logical const" like C++, so not all logically const operations can be declared const. opEquals is always logically const, but it cannot always be physically const.
Then you cannot call opEquals with a const or immutable object. To say you're not allowed to compare const or immutable objects is pretty limited.If you think it's worth doing, you can always cast. Despite the label of 'undefined behavior', I believe the compiler will behave fine if you do that (it can't really optimize out const calls).
Not safe. The set of const objects includes immutable objects, and immutable objects can be accessed from multiple threads at the same time, but caching is not (by default) thread-safe.
You have to take steps to make sure it is. IMO, your code actually is thread safe because overwriting an int with the same int will not result in any races. -Steve
Oct 19 2010









Jonathan M Davis <jmdavisProg gmx.com> 