https://issues.dlang.org/show_bug.cgi?id=18657
Issue ID: 18657 Summary: std.range and std.algorithm can't handle refRange Product: D Version: D2 Hardware: All OS: All Status: NEW Severity: normal Priority: P1 Component: phobos Assignee: nob...@puremagic.com Reporter: ag0ae...@gmail.com Five examples: ---- void main() { import std.range: refRange; import std.stdio; { import std.algorithm.iteration: group; string s = "foo"; auto r = refRange(&s).group; writeln(r.save); /* Prints "[Tuple!(dchar, uint)('f', 1), Tuple!(dchar, uint)('o', 2)]". Ok. */ writeln(r.save); /* Should print the same as the line above. Actually prints "[Tuple!(dchar, uint)('f', 1)]". */ } { import std.range: chain; string s = "foo"; auto r = refRange(&s).chain("bar"); writeln(r.save); /* Should print "foobar". Actually prints "bar". */ } { import std.range: choose; string s = "foo"; auto r = choose(true, refRange(&s), "bar"); writeln(r); /* Should print "foo". Actually prints nothing. */ } { import std.range: cycle, take; string s = "foo"; auto r = refRange(&s).cycle.take(4); writeln(r.save); /* Prints "foof". Ok. */ writeln(r.save); /* Should print "foof", too. Actually prints "oofo". */ } { import std.algorithm.iteration: splitter; string s = "foobar"; auto r = refRange(&s).splitter!(c => c == 'b'); writeln(r.save); /* Prints "[foo, ar]". Ok. */ writeln(r.save); /* Should print the same. Actually crashes with an AssertError. */ } } ---- Most probably, there are more Phobos functions that can't handle refRange. I haven't checked them all. The root of the problem is RefRange's opAssign. Instead of just changing the reference, it actually overwrites the referenced range. That leads to surprising behavior: ---- void main() { import std.range; import std.stdio; string s = "foo"; auto r = refRange(&s); auto r2 = r; r2 = r2.save; /* Surprising: Effectively just does `s = s;` (i.e., nothing). */ r2.popFront(); writeln(r); /* Surprising: Prints "oo". */ } ---- Note that `r2 = r; r2 = r2.save;` is what you typically do in a postblit function. If RefRange's custom opAssign is removed, all the examples just work. Unfortunately, the surprising behavior is deliberate, and not just a bug. The docs on RefRange.opAssign say [1]: > This does not assign the pointer of rhs to this RefRange. > Rather it assigns the range pointed to by rhs to the range pointed > to by this RefRange. This is because any operation on a RefRange is > the same is if it occurred to the original range. The issue comes down to whether RefRange should be allowed to have its funky opAssign, or if range-handling code should be allowed to assume that assignment does the obvious thing. [1] https://dlang.org/phobos/std_range.html#.RefRange.opAssign --