www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - opEquals the template?

reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
There is a debate in a sub-thread about logical const on whether  
Object.opEquals should be const or mutable.

Right now, it's mutable.  This disallows comparison between const objects  
(except it is technically allowed because the compiler ignores const  
correctness for this).

I think it should be const.

However, making it const disallows opEquals functions which mutate the  
object (for lazy loading or caching purposes).  I question that this is a  
major concern, but I have discovered an easy fix that appeases all.

If we make opEquals a template (not Object.opEquals, but the free function  
opEquals(Object lhs, Object rhs) ), then the template has access to the  
full type information of the derived object.  This means that  
Object.opEquals can be exclusively const, and you can overload this with a  
mutable version, and everything just works.

I tried it out by making my copy of druntime use a template (it's actually  
very few lines change).  Although I needed to use casts to compile some  
parts of phobos (some opEquals calls depend on the compiler ignoring const  
guarantees as mentioned above), when compiling a simple test program, I  
can prove it works without compiler changes.

Along with the ability to have both const and non-const (and even  
technically immutable) opEquals, we have the following drawbacks and  
benefits:

1. template bloat.  Every combination of two object types being compared  
will generate a new template.
2. inlining of opEquals.  Both the free function template, and possibly a  
final-declared opEquals on a dervied object could be inlined.
3. possible specialization of opEquals(MyDerivedType obj).  I don't  
remember exactly the overloading rules, so this may be impossible.
4. Fixes the issue with interface comparison (see bug  
http://d.puremagic.com/issues/show_bug.cgi?id=4088), not in the best way,  
but it would be better than the current situation.

What do people think?

-Steve
Sep 30 2011
next sibling parent kenji hara <k.hara.pg gmail.com> writes:
2011/10/1 Steven Schveighoffer <schveiguy yahoo.com>:
 There is a debate in a sub-thread about logical const on whether
 Object.opEquals should be const or mutable.

 Right now, it's mutable. =A0This disallows comparison between const objec=

 (except it is technically allowed because the compiler ignores const
 correctness for this).

 I think it should be const.

 However, making it const disallows opEquals functions which mutate the
 object (for lazy loading or caching purposes). =A0I question that this is=

 major concern, but I have discovered an easy fix that appeases all.

 If we make opEquals a template (not Object.opEquals, but the free functio=

 opEquals(Object lhs, Object rhs) ), then the template has access to the f=

 type information of the derived object. =A0This means that Object.opEqual=

 be exclusively const, and you can overload this with a mutable version, a=

 everything just works.

 I tried it out by making my copy of druntime use a template (it's actuall=

 very few lines change). =A0Although I needed to use casts to compile some
 parts of phobos (some opEquals calls depend on the compiler ignoring cons=

 guarantees as mentioned above), when compiling a simple test program, I c=

 prove it works without compiler changes.

 Along with the ability to have both const and non-const (and even
 technically immutable) opEquals, we have the following drawbacks and
 benefits:

 1. template bloat. =A0Every combination of two object types being compare=

 will generate a new template.
 2. inlining of opEquals. =A0Both the free function template, and possibly=

 final-declared opEquals on a dervied object could be inlined.
 3. possible specialization of opEquals(MyDerivedType obj). =A0I don't rem=

 exactly the overloading rules, so this may be impossible.
 4. Fixes the issue with interface comparison (see bug
 http://d.puremagic.com/issues/show_bug.cgi?id=3D4088), not in the best wa=

 but it would be better than the current situation.

 What do people think?

 -Steve

I think your suggestion is almost same as mine. Discussions: https://github.com/D-Programming-Language/phobos/pull/262 Implementations: https://github.com/D-Programming-Language/druntime/pull/72 My patch does not support #4 (interface comparison), but others are same. Kenji Hara
Sep 30 2011
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 30 Sep 2011 11:48:02 -0400, kenji hara <k.hara.pg gmail.com> wrote:

 2011/10/1 Steven Schveighoffer <schveiguy yahoo.com>:
 There is a debate in a sub-thread about logical const on whether
 Object.opEquals should be const or mutable.

 Right now, it's mutable.  This disallows comparison between const  
 objects
 (except it is technically allowed because the compiler ignores const
 correctness for this).

 I think it should be const.

 However, making it const disallows opEquals functions which mutate the
 object (for lazy loading or caching purposes).  I question that this is  
 a
 major concern, but I have discovered an easy fix that appeases all.

 If we make opEquals a template (not Object.opEquals, but the free  
 function
 opEquals(Object lhs, Object rhs) ), then the template has access to the  
 full
 type information of the derived object.  This means that  
 Object.opEquals can
 be exclusively const, and you can overload this with a mutable version,  
 and
 everything just works.

 I tried it out by making my copy of druntime use a template (it's  
 actually
 very few lines change).  Although I needed to use casts to compile some
 parts of phobos (some opEquals calls depend on the compiler ignoring  
 const
 guarantees as mentioned above), when compiling a simple test program, I  
 can
 prove it works without compiler changes.

 Along with the ability to have both const and non-const (and even
 technically immutable) opEquals, we have the following drawbacks and
 benefits:

 1. template bloat.  Every combination of two object types being compared
 will generate a new template.
 2. inlining of opEquals.  Both the free function template, and possibly  
 a
 final-declared opEquals on a dervied object could be inlined.
 3. possible specialization of opEquals(MyDerivedType obj).  I don't  
 remember
 exactly the overloading rules, so this may be impossible.
 4. Fixes the issue with interface comparison (see bug
 http://d.puremagic.com/issues/show_bug.cgi?id=4088), not in the best  
 way,
 but it would be better than the current situation.

 What do people think?

 -Steve

I think your suggestion is almost same as mine. Discussions: https://github.com/D-Programming-Language/phobos/pull/262 Implementations: https://github.com/D-Programming-Language/druntime/pull/72 My patch does not support #4 (interface comparison), but others are same.

Yes, you did almost exactly what I did, except I made it valid to call mutable opEquals for two unlike objects. That is, I think there is no point for the static if in the middle, just do lhs.opEquals(rhs) && rhs.opEquals(lhs) for all cases. -Steve
Sep 30 2011
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, September 30, 2011 11:35:51 Steven Schveighoffer wrote:
 There is a debate in a sub-thread about logical const on whether
 Object.opEquals should be const or mutable.
 
 Right now, it's mutable.  This disallows comparison between const objects
 (except it is technically allowed because the compiler ignores const
 correctness for this).
 
 I think it should be const.
 
 However, making it const disallows opEquals functions which mutate the
 object (for lazy loading or caching purposes).  I question that this is a
 major concern, but I have discovered an easy fix that appeases all.
 
 If we make opEquals a template (not Object.opEquals, but the free function
 opEquals(Object lhs, Object rhs) ), then the template has access to the
 full type information of the derived object.  This means that
 Object.opEquals can be exclusively const, and you can overload this with a
 mutable version, and everything just works.
 
 I tried it out by making my copy of druntime use a template (it's actually
 very few lines change).  Although I needed to use casts to compile some
 parts of phobos (some opEquals calls depend on the compiler ignoring const
 guarantees as mentioned above), when compiling a simple test program, I
 can prove it works without compiler changes.
 
 Along with the ability to have both const and non-const (and even
 technically immutable) opEquals, we have the following drawbacks and
 benefits:
 
 1. template bloat.  Every combination of two object types being compared
 will generate a new template.
 2. inlining of opEquals.  Both the free function template, and possibly a
 final-declared opEquals on a dervied object could be inlined.
 3. possible specialization of opEquals(MyDerivedType obj).  I don't
 remember exactly the overloading rules, so this may be impossible.
 4. Fixes the issue with interface comparison (see bug
 http://d.puremagic.com/issues/show_bug.cgi?id=4088), not in the best way,
 but it would be better than the current situation.
 
 What do people think?

Sounds good to me. Even better, it would make it possible to have opEquals be pure too, since a derived class could make it pure and then the opEquals template could be pure, allowing for the use of == in pure functions when dealing with classes. - Jonathan M Davis
Sep 30 2011
prev sibling next sibling parent kenji hara <k.hara.pg gmail.com> writes:
2011/10/1 Steven Schveighoffer <schveiguy yahoo.com>:
 Yes, you did almost exactly what I did, except I made it valid to call
 mutable opEquals for two unlike objects.

 That is, I think there is no point for the static if in the middle, just do
 lhs.opEquals(rhs) && rhs.opEquals(lhs) for all cases.

Your suggestion makes object.opEquals more simple. It is good. I worried that direct comparison partially breaks "Liskov substitution principle", but D can detect "HiddenFuncError" in compile time, then it is not so bug prone. ---- // comparing with any other types returns true class X { bool opEquals(const Object o) const { return true; } } // provide special opEquals against class X class C { bool opEquals(const Object o) const { return true; } bool opEquals(const X o) const { return true; } } // provide special opEquals against class X too, and rewrite its behavior class D : C { override bool opEquals(const Object o) const { if (auto x = cast(const X) o) return opEquals(x); return true; } override bool opEquals(const X o) const pure nothrow { return false; } // In class D, we should define two opEquals, otherwise raises HiddenFuncError in compile time. } void main() { auto x = new X(); { auto c = new C(); assert(c == x); auto oc = c, ox = x; assert(oc == ox); } { auto d = new D(); assert(d != x); // direct comparison C cd = d; assert(cd is d); assert(cd != x); // instance based behavior, it is consistent Object od = d, ox = x; assert(od != x); // opEquals(const Object) const is used, it is required the right definition of opEquals in class D } } ---- We still define the two opEquals properly (e.g. in class D), but I think it is right cost to specialization. Kenji Hara
Sep 30 2011
prev sibling next sibling parent kenji hara <k.hara.pg gmail.com> writes:
Additionally, I think we should change the "HiddenFuncError" from
deprecated feature to removed feature.

Kenji Hara
Sep 30 2011
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
Is opEquals not allowed to be a template in a struct? Because I was
using it in my library, and it sort-of worked, up until it started
causing some really weird bugs where a ctor was called instead of
opCast. This results in an error code returned by the compiler:

struct Foo
{
    bool opEquals(T)(ref const T rhs) const
    {
        return this == this;
    }
}

void main()
{
    auto foo = Foo();
    assert(foo == foo);
}

Exit code: -1073741819

And yet it worked fine in a larger module until I've hit the random
buggy behavior.. I don't understand..
Oct 01 2011
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, October 02, 2011 03:39:29 Andrej Mitrovic wrote:
 Is opEquals not allowed to be a template in a struct? Because I was
 using it in my library, and it sort-of worked, up until it started
 causing some really weird bugs where a ctor was called instead of
 opCast. This results in an error code returned by the compiler:
 
 struct Foo
 {
     bool opEquals(T)(ref const T rhs) const
     {
         return this == this;
     }
 }
 
 void main()
 {
     auto foo = Foo();
     assert(foo == foo);
 }
 
 Exit code: -1073741819
 
 And yet it worked fine in a larger module until I've hit the random
 buggy behavior.. I don't understand..

Struct's are a completely different issue. opEquals for structs is not handled the same way as it is for classes. Their biggest issue is http://d.puremagic.com/issues/show_bug.cgi?id=3659 and that was finally fixed a couple of days ago. There may be further bugs with it, but the issue that Steven is bringing up is about classes, not structs. - Jonathan M Davis
Oct 01 2011
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
*correction*, the error code is return by the application itself. I'm
filing this anyway.
Oct 01 2011
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
Ok so it seems there was a restriction before pull 373/70. I'll hold
on to any bug reports until I've checked these. Sorry for intruding,
didn't want to create a whole new topic since it's relevant (kinda).
Oct 01 2011
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 10/2/11, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:
     bool opEquals(T)(ref const T rhs) const
     {
         return this == this;
     }

This right here gets an award of some kind. I'll show myself the exit doors. LOL.
Oct 01 2011
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, October 02, 2011 04:43:34 Andrej Mitrovic wrote:
 On 10/2/11, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:
     bool opEquals(T)(ref const T rhs) const
     {
     
         return this == this;
     
     }

This right here gets an award of some kind. I'll show myself the exit doors. LOL.

I don't know. If you're _that_ confused, you'll probably end up in the bathroom instead. ;) LOL. Yeah well, we all make dumb mistakes from time to time. - Jonathan M Davis
Oct 01 2011
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
Guilty of copy-pasting and editing on the fly. Well originally it was
a templated overload of opEquals that used opCast behind-the-scenes to
convert `rhs` to a native type and then use another opEquals to do the
actual comparison:
return this == cast(typeof(this))rhs;

It might not be the most efficient code, but it saves me time from typing.
Due to some funky bug, that cast right there actually calls a ctor
that can take `rhs` as a parameter. I'll just have to wait for 2.056
so templated opEquals can work properly.
Oct 01 2011
prev sibling parent kenji hara <k.hara.pg gmail.com> writes:
I have updated my pull requests.

https://github.com/D-Programming-Language/dmd/pull/387
https://github.com/D-Programming-Language/druntime/pull/72
https://github.com/D-Programming-Language/phobos/pull/262

New improvements:
- Enable direct comparison with mutable opEquals.
- Enable interface comparison for fixing issue 4088.
- Add changes for fixing issue 3789. It requires const equality
comparison for member classes.

Kenji Hara

2011/10/1 Steven Schveighoffer <schveiguy yahoo.com>:
 There is a debate in a sub-thread about logical const on whether
 Object.opEquals should be const or mutable.

 Right now, it's mutable. =A0This disallows comparison between const objec=

 (except it is technically allowed because the compiler ignores const
 correctness for this).

 I think it should be const.

 However, making it const disallows opEquals functions which mutate the
 object (for lazy loading or caching purposes). =A0I question that this is=

 major concern, but I have discovered an easy fix that appeases all.

 If we make opEquals a template (not Object.opEquals, but the free functio=

 opEquals(Object lhs, Object rhs) ), then the template has access to the f=

 type information of the derived object. =A0This means that Object.opEqual=

 be exclusively const, and you can overload this with a mutable version, a=

 everything just works.

 I tried it out by making my copy of druntime use a template (it's actuall=

 very few lines change). =A0Although I needed to use casts to compile some
 parts of phobos (some opEquals calls depend on the compiler ignoring cons=

 guarantees as mentioned above), when compiling a simple test program, I c=

 prove it works without compiler changes.

 Along with the ability to have both const and non-const (and even
 technically immutable) opEquals, we have the following drawbacks and
 benefits:

 1. template bloat. =A0Every combination of two object types being compare=

 will generate a new template.
 2. inlining of opEquals. =A0Both the free function template, and possibly=

 final-declared opEquals on a dervied object could be inlined.
 3. possible specialization of opEquals(MyDerivedType obj). =A0I don't rem=

 exactly the overloading rules, so this may be impossible.
 4. Fixes the issue with interface comparison (see bug
 http://d.puremagic.com/issues/show_bug.cgi?id=3D4088), not in the best wa=

 but it would be better than the current situation.

 What do people think?

 -Steve

Oct 15 2011