Thread overview | ||||||||
---|---|---|---|---|---|---|---|---|
|
November 18, 2020 implementing default opCmp | ||||
---|---|---|---|---|
| ||||
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 |
November 18, 2020 Re: implementing default opCmp | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | 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; } } |
November 18, 2020 Re: implementing default opCmp | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | 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)); } |
November 19, 2020 Re: implementing default opCmp | ||||
---|---|---|---|---|
| ||||
Posted in reply to Paul Backus | 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
|
November 19, 2020 Re: implementing default opCmp | ||||
---|---|---|---|---|
| ||||
Posted in reply to ag0aep6g | 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
|
November 19, 2020 Re: implementing default opCmp | ||||
---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | On 11/19/20 6:12 AM, Steven Schveighoffer wrote: > On 11/18/20 6:06 PM, ag0aep6g wrote: >> 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 That's what I use as well. S can be replaced with something like 'typeof(this)' (or perhaps 'ref const(typeof(this))' and throw some inout in there :) ) and the whole thing can be mixed-in whereever needed. Ali |
Copyright © 1999-2021 by the D Language Foundation