www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - std.typecons rebindable + tuple with const class gives warning

reply Saurabh Das <saurabh.das gmail.com> writes:
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?
Feb 04 2021
parent reply tsbockman <thomas.bockman gmail.com> writes:
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.
Feb 04 2021
parent Saurabh Das <saurabh.das gmail.com> writes:
On Thursday, 4 February 2021 at 20:40:43 UTC, tsbockman wrote:
 TLDR; Either make `c` mutable, or override/overload the `C` 
 associative array support methods `toHash` and `opEquals` to 
 support `const(C)` objects.
This solved my issue. I finally understood why this was happening after digging in to the tuple code and hence understood your proposed solutions. Thank you so much! :) Saurabh
Feb 17 2021