There's a Dscanner check for this:
std/range/package.d(5748:12)[warn]: 'Cyclic' has method 'opEquals', but not 'toHash'.
std/internal/test/dummyrange.d(363:8)[warn]: 'TestFoo' has method 'opEquals', but not 'toHash'.
std/random.d(251:8)[warn]: 'LinearCongruentialEngine' has method 'opEquals', but not 'toHash'.
std/random.d(995:8)[warn]: 'XorshiftEngine' has method 'opEquals', but not 'toHash'.
std/datetime.d(9240:8)[warn]: 'Date' has method 'opCmp', but not 'opEquals'.
std/datetime.d(13548:8)[warn]: 'TimeOfDay' has method 'opCmp', but not 'opEquals'.
std/datetime.d(14822:8)[warn]: 'DateTime' has method 'opCmp', but not 'opEquals'.
std/datetime.d(31723:14)[warn]: 'StopWatch' has method 'opEquals', but not 'toHash'.
std/complex.d(99:8)[warn]: 'Complex' has method 'opEquals', but not 'toHash'.
std/array.d(162:12)[warn]: 'Foo' has method 'opEquals', but not 'toHash'.
std/functional.d(466:12)[warn]: 'Foo' has method 'opEquals', but not 'toHash'.
std/container/rbtree.d(738:13)[warn]: 'RedBlackTree' has method 'opEquals', but not 'toHash'.
std/container/array.d(249:8)[warn]: 'Array' has method 'opEquals', but not 'toHash'.
std/container/dlist.d(170:8)[warn]: 'DList' has method 'opEquals', but not 'toHash'.
std/container/slist.d(54:8)[warn]: 'SList' has method 'opEquals', but not 'toHash'.
std/numeric.d(149:8)[warn]: 'CustomFloat' has method 'opCmp', but not 'opEquals'.
std/json.d(101:8)[warn]: 'JSONValue' has method 'opEquals', but not 'toHash'.
std/socket.d(1486:7)[warn]: 'InternetAddress' has method 'opEquals', but not 'toHash'.
std/algorithm/searching.d(3235:19)[warn]: 'S2' has method 'opEquals', but not 'toHash'.
std/uni.d(1205:8)[warn]: 'PackedArrayViewImpl' has method 'opEquals', but not 'toHash'.
std/uni.d(1361:16)[warn]: 'SliceOverIndexed' has method 'opEquals', but not 'toHash'.
std/uni.d(1899:15)[warn]: 'CodepointInterval' has method 'opEquals', but not 'toHash'.
std/uni.d(3095:8)[warn]: 'CowArray' has method 'opEquals', but not 'toHash'.
Comment #1 by jack — 2018-03-28T13:08:50Z
I'm turning this into a tracking issue.
Comment #2 by n8sh.secondary — 2018-07-02T19:11:17Z
Half of this is wrong. For a type to be usable as a key in an associative array it must be true that "a == b" implies "a.toHash() == b.toHash()", so when there is a non-default `==` there should also be a custom `toHash` to maintain this property. But the reverse is not true: defining a non-default `toHash` does not require a custom `opEquals`, because the default `==` is already the strictest possible condition that satisfies "a == a" (since structs can be relocated).
Comment #3 by schveiguy — 2018-07-03T11:46:01Z
No, toHash doesn't necessarily have to follow the semantics of default opEquals.
Example:
struct S1(bool customTohash)
{
string s;
static if(customTohash)
{
size_t toHash() const
{
return cast(size_t)s.ptr + s.length; // use string identity
}
/+ really should define this
bool opEquals(const(S1) other) const { return other.s is s; }
+/
}
}
void main()
{
bool[S1!false] aa1;
aa1[S1!false("hello".idup)] = true;
aa1[S1!false("hello".idup)] = true;
assert(aa1.length == 1);
bool[S1!true] aa2;
aa2[S1!true("hello".idup)] = true;
aa2[S1!true("hello".idup)] = true;
assert(aa2.length == 1); // fails
}
Comment #4 by schveiguy — 2018-07-03T11:49:46Z
(In reply to Steven Schveighoffer from comment #3)
> bool[S1!true] aa2;
> aa2[S1!true("hello".idup)] = true;
> aa2[S1!true("hello".idup)] = true;
> assert(aa2.length == 1); // fails
> }
The assert isn't telling the right story, really it should be that none of the keys are equal to each other. This is a better example:
auto s1 = S1!true("hello".idup);
auto s2 = S1!true("hello".idup);
assert(s1 == s2)
aa2[s1] = true;
aa2[s2] = true;
assert(aa2.length == 1);
Comment #5 by robert.schadek — 2024-12-01T16:29:42Z