std.typecons rebindable + tuple with const class gives warning

tsbockman thomas.bockman at gmail.com
Thu Feb 4 20:40:43 UTC 2021


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.


More information about the Digitalmars-d-learn mailing list