www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Mutable ForwardRange save() method not callable using const object

reply Timoses <timosesu gmail.com> writes:
Hey,

I'm fiddling around with ranges a bit and am wondering why save 
is not callable on a const object:

     class Range
     {
         ForwardRange!(const uint) offsets;

         this(const S s)
         {
             this.offsets = s.s.map!(e => e.i).inputRangeObject;
         }

         this(const Range a) // ERROR line 22
         {
             this.offsets = a.offsets.save;
         }

         Range save()
         {
             return new Range(this);
         }
     }
     struct I
     {
         uint i;
     }
     struct S
     {
         I[] s = [I(1), I(2), I(3)];
     }

     unittest
     {
         S s;
         auto a = new Range(s);
     }

onlineapp.d(22): Error: mutable method 
std.range.interfaces.ForwardRange!(const(uint)).ForwardRange.save 
is not callable using a const object
onlineapp.d(22):        Consider adding const or inout to 
std.range.interfaces.ForwardRange!(const(uint)).ForwardRange.save


In this case the Forwardrange Offsets is a
ForwardRange of MapResult(I => uint) of I[]

Since the type of MapResult.front is uint, the ForwardRange is 
essentially a uint[] array.

I could imagine that save is not marked as const because it is 
uncertain whether any indirections are part of the content..? But 
couldn't the compiler check that?

Could anybody explain what exactly is going on? Is there a reason 
the `save()` is not working on const objects?


Given that I haven't done much with ranges yet, any feedback on 
the above implementation is also greatly appreciated (it's just a 
reduced example).
Sep 04 2018
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/4/18 9:53 AM, Timoses wrote:
 Hey,
 
 I'm fiddling around with ranges a bit and am wondering why save is not 
 callable on a const object:
...
 I could imagine that save is not marked as const because it is uncertain 
 whether any indirections are part of the content..? But couldn't the 
 compiler check that?
But you have eliminated any visibility into the range itself -- you stuck it behind an interface.
 Could anybody explain what exactly is going on? Is there a reason the 
 `save()` is not working on const objects?
The trivial reason is because the method is not marked const: https://github.com/dlang/phobos/blob/master/std/range/interfaces.d#L153 The real reason is that given a const interface, everything inside it must be const as well. Making a copy of that makes a const copy, which by definition couldn't be assigned back to the original range (which is likely not const). At the very least, it should be inout. But in either case, the function needs to be properly marked. As general advice, I wouldn't expect const to work well with Ranges anyway -- const ranges are useless (you can't iterate them). So not much code is expecting to handle const, including the wrappers that Phobos provides. -Steve
Sep 04 2018
parent reply Timoses <timosesu gmail.com> writes:
On Tuesday, 4 September 2018 at 14:26:44 UTC, Steven 
Schveighoffer wrote:
 [...]
 As general advice, I wouldn't expect const to work well with 
 Ranges anyway -- const ranges are useless (you can't iterate 
 them). So not much code is expecting to handle const, including 
 the wrappers that Phobos provides.
Thanks, that sounds like some good advice. I've actually bumped into this right after when I had an immutable range which was supposed to fill in its cache when being iterated. My argument for why this should work is that "from the outside of the range" it is immutable, since accessing via opIndex will always give the same value. ... In the end, that'll lead nowhere. The immutable attribute would become a "promise" rather than an enforcement (or too hard for the compiler to enforce). Thanks for the clarification, Steve!
Sep 04 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Tuesday, September 4, 2018 9:22:26 AM MDT Timoses via Digitalmars-d-learn 
wrote:
 On Tuesday, 4 September 2018 at 14:26:44 UTC, Steven

 Schveighoffer wrote:
 [...]
 As general advice, I wouldn't expect const to work well with
 Ranges anyway -- const ranges are useless (you can't iterate
 them). So not much code is expecting to handle const, including
 the wrappers that Phobos provides.
Thanks, that sounds like some good advice. I've actually bumped into this right after when I had an immutable range which was supposed to fill in its cache when being iterated. My argument for why this should work is that "from the outside of the range" it is immutable, since accessing via opIndex will always give the same value. ... In the end, that'll lead nowhere. The immutable attribute would become a "promise" rather than an enforcement (or too hard for the compiler to enforce).
That's simply not how const or immutable work in D though. They're actual compiler guarantees, and it's undefined behavior to ever cast away const or immutable and mutate the object. D's const is fundamentally different from C++'s const in this regard. Not only is a const that doesn't provide compiler guarantees useless according to Walter (though that's obviously debatable), but the nature of immutable pretty much requires it. immutable objects are implicitly shared, and in principle, they could end up in ROM. So, even if your object is const, there's no guarantee that it's actually mutable underneath the hood. The rigidness of D's const and immutable are both a blessing and a curse. But the net result is we really can't have a range API that works with them without more of a head/tail model (whereas the current API mutates in place), and even if we had a head/tail model, it would still get pretty hairy thanks to issues like reference counting. So, as Steven points out, const and ranges don't go together at all. The result is that a number of us use stuff like const and inout pretty sparingly (at least outside of primitive types): http://jmdavisprog.com/articles/why-const-sucks.html - Jonathan M Davis
Sep 04 2018