www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Why struct opEquals must be const?

reply "Nick Sabalausky" <a a.a> writes:
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
next sibling parent reply "Denis Koroskin" <2korden gmail.com> writes:
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
parent Jonathan M Davis <jmdavisProg gmx.com> writes:
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
prev sibling next sibling parent so <so so.do> writes:
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
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
parent reply Pelle <pelle.mansson gmail.com> writes:
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
next sibling parent reply Pelle <pelle.mansson gmail.com> writes:
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
parent Pelle <pelle.mansson gmail.com> writes:
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
prev sibling parent reply Rainer Deyke <rainerd eldwood.com> writes:
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
parent Rainer Deyke <rainerd eldwood.com> writes:
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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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