www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - implementing default opCmp

reply Steven Schveighoffer <schveiguy gmail.com> writes:
I have a struct like this:

struct S
{
    int x;
    int y;
}

and I want a default comparison. The problem is, that comparison doesn't 
have a default, and requires I implement opCmp. While this is useful for 
the compiler, there's no default I know of that is an easy one-liner.

The truth is, I'm not entirely caring what order these things come out 
in. I just want them to be defined as having an order given that all the 
members have a defined order. My expectation is that a default opCmp 
would look like:

int opCmp(S other)
{
    if(x == other.x)
    {
        if(y == other.y) return 0;
        return y < other.y ? -1 : 1;
    }
    return x < other.x ? -1 : 1;
}

But really, as long as there is something to do this easily I don't care 
what the ordering turns out to be.

I can do equality like:

return this.tupleof == other.tupleof;

I can do assignment like:

this.tupleof = other.tupleof;

How do I do something really simple for opCmp? I tried this it didn't work:

return this == other ? 0 :
     this.tupleof < other.tupleof ? -1 : 1;

-Steve
Nov 18 2020
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Wednesday, 18 November 2020 at 22:29:17 UTC, Steven 
Schveighoffer wrote:
 I have a struct like this:

 struct S
 {
    int x;
    int y;
 }

 and I want a default comparison. The problem is, that 
 comparison doesn't have a default, and requires I implement 
 opCmp. While this is useful for the compiler, there's no 
 default I know of that is an easy one-liner.
Here's a stab at a totally generic version that I haven't unit tested at all, except to verify that it works for your example struct S: auto cmp(T, U)(auto ref T lhs, auto ref U rhs) { import core.lifetime: forward; static if (__traits(compiles, lhs.opCmp(rhs))) return forward!lhs.opCmp(forward!rhs); else static if (__traits(compiles, rhs.opCmp(lhs))) return -forward!rhs.opCmp(forward!lhs); else return lhs < rhs ? -1 : lhs > rhs ? 1 : 0; } mixin template defaultOpCmp() { import std.traits: isAggregateType; static assert(isAggregateType!(typeof(this)), "opCmp can only be overloaded for aggregate types."); auto opCmp()(auto ref typeof(this) other) { import std.traits: ReturnType, CommonType, Fields; import std.meta: Map = staticMap; alias cmpType(T) = ReturnType!((T lhs, T rhs) => cmp(lhs, rhs)); alias Result = CommonType!(Map!(cmpType, Fields!(typeof(this)))); Result result; static foreach (i, _; typeof(this).tupleof) if (result == 0) result = cmp(this.tupleof[i], other.tupleof[i]); return result; } }
Nov 18 2020
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 11/18/20 6:02 PM, Paul Backus wrote:
 On Wednesday, 18 November 2020 at 22:29:17 UTC, Steven Schveighoffer wrote:
 I have a struct like this:

 struct S
 {
    int x;
    int y;
 }

 and I want a default comparison. The problem is, that comparison 
 doesn't have a default, and requires I implement opCmp. While this is 
 useful for the compiler, there's no default I know of that is an easy 
 one-liner.
Here's a stab at a totally generic version that I haven't unit tested at all, except to verify that it works for your example struct S: auto cmp(T, U)(auto ref T lhs, auto ref U rhs) {     import core.lifetime: forward;     static if (__traits(compiles, lhs.opCmp(rhs)))         return forward!lhs.opCmp(forward!rhs);     else static if (__traits(compiles, rhs.opCmp(lhs)))         return -forward!rhs.opCmp(forward!lhs);     else         return lhs < rhs ? -1 : lhs > rhs ? 1 : 0; } mixin template defaultOpCmp() {     import std.traits: isAggregateType;     static assert(isAggregateType!(typeof(this)),         "opCmp can only be overloaded for aggregate types.");     auto opCmp()(auto ref typeof(this) other)     {         import std.traits: ReturnType, CommonType, Fields;         import std.meta: Map = staticMap;         alias cmpType(T) = ReturnType!((T lhs, T rhs) => cmp(lhs, rhs));         alias Result = CommonType!(Map!(cmpType, Fields!(typeof(this))));         Result result;         static foreach (i, _; typeof(this).tupleof)             if (result == 0)                 result = cmp(this.tupleof[i], other.tupleof[i]);         return result;     } }
Yeah, something like this might be useful in druntime. But it makes you wonder if we wouldn't be better off without opCmp but instead with opBinary(string s : "<") and friends. One thing that sucks is that opCmp might do more operations than are necessary for the actual comparison, because it has to generate the numeric result. -Steve
Nov 19 2020
prev sibling parent reply ag0aep6g <anonymous example.com> writes:
On Wednesday, 18 November 2020 at 22:29:17 UTC, Steven 
Schveighoffer wrote:
 How do I do something really simple for opCmp? I tried this it 
 didn't work:

 return this == other ? 0 :
     this.tupleof < other.tupleof ? -1 : 1;
std.typecons.Tuple has opCmp. So this works: int opCmp(S other) { import std.typecons: tuple; return tuple(this.tupleof).opCmp(tuple(other.tupleof)); }
Nov 18 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 11/18/20 6:06 PM, ag0aep6g wrote:
 On Wednesday, 18 November 2020 at 22:29:17 UTC, Steven Schveighoffer wrote:
 How do I do something really simple for opCmp? I tried this it didn't 
 work:

 return this == other ? 0 :
     this.tupleof < other.tupleof ? -1 : 1;
std.typecons.Tuple has opCmp. So this works:     int opCmp(S other)     {         import std.typecons: tuple;         return tuple(this.tupleof).opCmp(tuple(other.tupleof));     }
Ah, excellent solution! I hadn't thought of that. -Steve
Nov 19 2020
parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 11/19/20 6:12 AM, Steven Schveighoffer wrote:
 On 11/18/20 6:06 PM, ag0aep6g wrote:
 =C2=A0=C2=A0=C2=A0=C2=A0 int opCmp(S other)
 =C2=A0=C2=A0=C2=A0=C2=A0 {
 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 import std.typecons: =
tuple;
 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 return tuple(this.tup=
leof).opCmp(tuple(other.tupleof));
 =C2=A0=C2=A0=C2=A0=C2=A0 }
=20 =20 Ah, excellent solution! I hadn't thought of that. =20 -Steve
That's what I use as well. S can be replaced with something like 'typeof(this)' (or perhaps 'ref=20 const(typeof(this))' and throw some inout in there :) ) and the whole=20 thing can be mixed-in whereever needed. Ali
Nov 19 2020