Must ranges support `r1 = r2;`?

Simen Kjærås simen.kjaras at gmail.com
Sat Mar 24 22:36:55 UTC 2018


On Saturday, 24 March 2018 at 21:44:35 UTC, ag0aep6g wrote:
> Long version: <https://issues.dlang.org/show_bug.cgi?id=18657> 
> ("std.range and std.algorithm can't handle refRange").
>
> Short version: With two `std.range.RefRange`s, `r1 = r2;` does 
> not what other Phobos code expects.
>
> Question is: Who's at fault? Where do I fix this? Do ranges 
> have to support assignment as expected - even though std.range 
> doesn't mention it? Or should range-handling code never do that 
> - even though it comes naturally and is widespread currently?

It seems to me you've misunderstood what refRange actually does. 
 From bugzilla:

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". */
}

None of this is surprising. When you call r2.popFront(), it's 
being forwarded to s.popFront. That's what refRange does, it's 
what it's supposed to do, and it's the only thing it could 
sensibly do.

This is conceptually what refRange does:

struct RefRange(R) {
     R* innerRange;
     this(R* innerRange) {
         this.innerRange = innerRange;
     }
     void popFront() {
         innerRange.popFront();
     }
     @property auto front() {
         return innerRange.front;
     }
     @property bool empty() {
         return innerRange.empty;
     }
}

In other words:

unittest {
     import std.range : refRange;
     import std.algorithm.comparison : equal;
     import std.array : popFront;

     auto a = "foo";
     auto b = refRange(&a);

     // Popping off the refRange pops off the original range.
     b.popFront();
     assert(a == "oo");

     // Popping off the original range pops off the refRange.
     a.popFront();
     assert(b.equal("o"));

     // b.equal("o") above had to iterate over the range,
     // thereby calling popFront until empty() returned true,
     // so the range is now empty.
     assert(a == "");
     assert(b.equal(""));
}

So when you do this:

     string s = "foo";
     auto r = refRange(&s).group;
     writeln(r.save);

r.save is going to create a new Group range, which contains a 
RefRange, which ultimately points to s. writeln() then repeatedly 
calls popFront(). This mutates s, as specified above.

You do have a point in the bug report on this point: calling 
writeln(r.save) again, should not write [Tuple!(dchar, uint)('f', 
1)], but just [], since the underlying range is now empty. This 
is a bug.

The 'chain', 'choose', and 'cycle' examples are an effect of 
strings not being random access ranges. I'm uncertain if we 
should call this a bug, but I agree the behavior is unexpected, 
so we probably should.

The splitter example is of course a bug. The crash, that is. The 
expected behavior is that the first writeln prints [foo, ar], 
which it does, and that the second print [].

--
   Simen




More information about the Digitalmars-d mailing list