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
