| |
| Posted by tsbockman in reply to Saurabh Das | PermalinkReply |
|
tsbockman
Posted in reply to Saurabh Das
| On Thursday, 4 February 2021 at 08:16:06 UTC, Saurabh Das wrote:
> This code:
>
> void main()
> {
> import std.typecons : rebindable, tuple;
> const c = new C();
> auto t = tuple(c.rebindable);
> }
>
> class C
> {
> }
>
> When compiled with DMD 2.095.0 gives a warning:
>
> Warning: struct Rebindable has method toHash, however it cannot be called with const(Rebindable!(const(C))) this.
>
> What is causing this? How can this warning be resolved?
`Rebindable!(C).toHash` forwards to `C.toHash`, which is inherited from `Object.toHash`, which has the type: `nothrow @trusted ulong()` according to pragma(msg, typeof(Object.toHash));` That type signature forbids calling `Object.toHash` on a mutable object. (An example of why this might be a valid design choice, would be if computing the hash is expensive and so the result is cached, which requires the freedom to mutate the instance.)
To fix this problem, you need to do at least one of the following:
1) Make `c` mutable by declaring it with `auto` or `C` instead of `const`. This is the only option if you cannot change the definition of `C`.
2) If you can change `C`, you can override `toHash` in `C` with a signature and implementation that do not require a mutable object. Examples:
A) If you don't need associative array support from `C` or its descendants at all, simply define `C.toHash` with more permissive (to the caller) attributes, and crash if it gets called unexpectedly:
class C
{
override size_t toHash() scope const pure @safe nothrow @nogc
{
assert(0, "Not implemented!");
}
}
B) If you want to support associative arrays by treating every instance of `C` as a unique value:
class C
{
override size_t toHash() scope const pure @safe nothrow @nogc
{
static assert(C.sizeof == size_t.sizeof);
union Bits
{
const(C) self;
const(size_t) hash;
}
return Bits(this).hash;
}
// opEquals must always be defined such that is consistent with toHash, such that this passes:
// if(this.opEquals(that))
// assert(this.toHash() == that.toHash());
override bool opEquals(Object that) scope const pure @safe nothrow @nogc
{
return (this is that);
}
bool opEquals(scope const(C) that) scope const pure @safe nothrow @nogc
{
return (this is that);
}
}
C) If you want to support associative arrays by treating separate instances of `C` as equal based on the contents of their data fields, then you will need to define appropriate `toHash` and `opEquals` implementations:
class Point
{
int x, y;
override size_t toHash() scope const pure @safe nothrow @nogc
{
return size_t(x) * size_t(y);
}
override bool opEquals(Object that) scope const pure @safe nothrow @nogc
{
if(auto thatPoint = cast(Point) that)
return opEquals(thatPoint);
else
return false;
}
bool opEquals(scope const(Point) that) scope const pure @safe nothrow @nogc
{
return (this.x == that.x) && (this.y == that.y);
}
}
TLDR; Either make `c` mutable, or override/overload the `C` associative array support methods `toHash` and `opEquals` to support `const(C)` objects.
|