Must ranges support `r1 = r2;`?

Jonathan M Davis newsgroup.d at jmdavisprog.com
Sun Mar 25 00:49:49 UTC 2018


On Sunday, March 25, 2018 00:28:32 ag0aep6g via Digitalmars-d wrote:
> On 03/25/2018 12:02 AM, Jonathan M Davis wrote:
> > auto range2 = range1; // now, range1 can't be used until it's assigned
> > to
> > range2.popFront();
> >
> > range1 = range2; // now, range2 can't be used until it's assigned to
> > range1.popFront();
> >
> > And I don't think that RefRange violates that.
>
> What RefRange violates is the assumption that range1 gets discarded, and
> that it simply gets overwritten by range2.
>
> Note that `auto range2 = range1;` does not have that problem, because
> it's initialization, not assignment. It doesn't call RefRange's funky
> opAssign.
>
> The various misbehaving Phobos functions assume that assignment works
> the same as initialization. But it doesn't with RefRange.

I'll have to think about it. IIRC, for RefRange to work as it's supposed to,
opAssign really does need to work the way that it does, but it's been a
while since I did much with RefRange, so I don't know. Either way,
assignment really isn't part of the range API. I don't think that any of the
range traits even test that assignment works on any level. So, it could be
argued that generic, range-based functions simply shouldn't ever be
assigning one range to another. I'm pretty sure that technically, a range
could @disable opAssign without violating the range API, though it wouldn't
surprise me if such a range then didn't work with a lot of existing code.

On some level, this falls into the same trap as save in that _usually_ save
is not required, but in some cases it is, so it's frequently the case that
save gets skipped when it's supposed to be called, and it only gets caught
when tests are added which use ranges which are reference types.

Similarly, it's quite common for range-based functions to assume that front
is not transitive, but occasionally, front is transitive, which then causes
problems. It's been argued that the solution for that is that any generic
code which retains the result of front after popFront is called needs to
call save before getting front in order to guarantee that the result of
front is independent, but doing that is not common practice at all and has
been debated on a number of occasions.

RefRange is operating in an area where the range API doesn't actually define
the semantics - either in the traits themselves or the behavior that's
documented as expected but not actually guaranteed by the traits (e.g. as is
the case with save - it's behavior can't be enforced, just documented).

So, I think that there's a good argument to be made that truly generic
range-based code should not be assigning one range to another, because the
range API does not actually require that ranges even support assignment, but
it also doesn't surprise me at all if folks do it quite a bit. We frequently
have the problem with ranges that folks assume certain behaviors based on
what the typical range does but which is not actually guaranteed by the
range API. And I don't know what the solution is for that. Certainly, the
result is that a lot of range-based code that exists doesn't actually work
with any range that matches the template constraints. Phobos does a far
better job of it than a lot of code, because it's written to be generic and
has lots of tests to catch corner cases that many folks never test for, but
we don't catch everything.

A lot of stuff would be cleaner if we could somehow require that all basic
input ranges be reference types and all forward ranges be value types (which
would include eliminating save from the range API), but that would be overly
restrictive in some cases, and it would be too disruptive a change now. And
for better or worse, RefRange probably wouldn't into that more restrictive
paradigm.

- Jonathan M Davis



More information about the Digitalmars-d mailing list