range.save

Jonathan M Davis via Digitalmars-d digitalmars-d at puremagic.com
Fri Nov 27 01:20:21 PST 2015


On Friday, 27 November 2015 at 08:53:26 UTC, Joseph Rushton 
Wakeling wrote:
> On Thursday, 26 November 2015 at 17:23:07 UTC, Andrei 
> Alexandrescu wrote:
>> So initially I thought a simple solution would be this:
>>
>> struct MyForwardRange
>> {
>>     enum bool isForward = true;
>>     ... empty, front, popFront ...
>> }
>>
>> enum isForwardRange(R) =
>>   is(typeof(R.isForward)) && R.isForward;
>>
>> Then there's no need for .save(), and isForward!R can be used 
>> for constraints etc. Reference forward ranges are not 
>> supported but then that's liberating (fewer complications), 
>> rare, and easy to work around by wrapping.
>
> By "reference forward range" do you mean a reference type (per 
> se) or any range that contains an internal reference type (e.g. 
> a range whose private data includes a dynamic array)?

Obviously, Andrei will have to answer to know what he meant, but 
with regards to ranges, I would consider a reference type to be 
one where in

auto copy = range;

doing anything to copy or range does the exact same thing to the 
other, because they refer to the exact same state. Something like 
save is required to get a separate range where popping elements 
from one will not affect the other.

Contrast that with a value type where copy and range are 
completely separate. And then there are ranges where the copy 
shares _some_ state with the original but not all, which as far 
as save goes is pretty much the same as a reference type but 
means that you can't rely on mutating one having the same effect 
on the other one either (the easiest example would be a range 
that would otherwise be a reference type but caches front).

Based on that view of things, dynamic arrays definitely are value 
types. Obviously, if you start doing stuff that would mutate 
their elements, then they aren't really value types, but if all 
you're doing is iterating over them, then they're essentially 
value types.

What we lose without save (or something else to replace it) is 
the ability to have any range types that aren't value types (or 
which essentially behave like them). So, a range which is a 
dynamic array or a simple wrapper around a dynamic array is fine, 
because copying the range is enough, but anything where a copy is 
not the same as save would have been will no longer work. Using 
postblit constructors like Sonke suggested solves _some_ of that 
problem, but not all of it, since that doesn't work with classes, 
and const doesn't work with postblit constructors, whereas we 
could make it work with save. The loss of classes poses the 
problem that non-templated functions can't really be made to work 
with ranges. And the loss of reference type ranges in general 
definitely is a problem for some uses cases.

But the more I look at the semantics involved, the more inclined 
I am to argue that trying to have the same code work for both 
value types and reference types is questionable at best. On top 
of that, pure input ranges almost need to be reference types 
(though they can currently work as pseudo-reference types at 
least some of the time), and forward ranges really need to be 
value types (at least as much as dynamic arrays are anyway) if we 
don't want to have to call save everywhere.

I'm starting to think that it would be better to have pure input 
ranges have to be reference types and forward ranges have to be 
value types and then be very careful about which functions work 
with both rather than simply treating all forward ranges like 
pure input ranges that can also be copied via save.

- Jonathan M Davis


More information about the Digitalmars-d mailing list