Example program:
import std.range: refRange, iota;
struct R1 {
auto r = iota(3);
alias r this;
}
void works() {
R1 r1;
foreach(x; refRange(&r1)) {}
assert(r1.empty);
}
struct R2 {
auto r = iota(3);
alias r this;
@property auto save() { return this; }
}
void explodes() {
R2 r2;
import std.range;
foreach(x; refRange(&r2)) {}
assert(r2.empty); // <-- BOOM
}
works() is fine.
explodes() fails on the assertion.
The only difference is that R2 defines save().
What happens is that the foreach calls opSlice (if it exists), which calls save. opSlice exists iff save exists.
Upgrading an existing input range to a forward range invisibly breaks any code that used foreach on a refRange of that range.
Comment #1 by ag0aep6g — 2018-04-30T00:12:28Z
Possibly a duplicate of issue 14619.
Comment #2 by eyal — 2018-04-30T08:25:02Z
It's related -- but that issue is about "foreach" behavior, and this issue is about RefRange behavior, which probably should not define opSlice (as mentioned in passing in issue 14619).
Comment #3 by schveiguy — 2018-04-30T12:30:35Z
There are 2 issues here. One is the same from 14619 -- the compiler is assuming opSlice on a valid range is a no-op.
The second issue is that for some reason opSlice is defined in RefRange iff save is defined. That makes no sense.
Fixing either of these issues will fix the problem I think.
Comment #4 by eyal — 2018-04-30T13:01:41Z
Indeed, and both are worth fixing.
So I think it makes sense to keep these 2 issues:
issue 14619 should fix compiler's foreach behavior (calling opSlice unnecessarily)
this issue should fix RefRange in phobos, to avoid defining opSlice().
Comment #5 by robert.schadek — 2024-12-01T16:33:36Z