On Friday, November 17, 2017 17:37:01 Tony via Digitalmars-d-learn wrote: > On Friday, 17 November 2017 at 07:40:35 UTC, Mike Parker wrote: > > You might also find use in this article (poorly adapted from > > Chapter 6 of Learning D by the publisher, but still readable): > > > > https://www.packtpub.com/books/content/understanding-ranges > > > >> makes a distinction about "range consumption" with regard to a > >> "reference type" or a "value type" and it isn't clear to me > >> why there would be a difference. > > > > With a value type, you're consuming a copy of the original > > range, so you can reuse it after. With a reference type, you're > > consuming the original range and therefore can't reuse it. > > > > > > ======== > > struct ValRange { > > > > int[] items; > > bool empty() @property { return items.length == 0; } > > int front() @property { return items[0]; } > > void popFront() { items = items[1 .. $]; } > > > > } > > > > class RefRange { > > > > int[] items; > > this(int[] src) { items = src; } > > bool empty() @property { return items.length == 0; } > > int front() @property { return items[0]; } > > void popFront() { items = items[1 .. $]; } > > > > } > > > > void main() { > > > > import std.stdio; > > > > int[] ints = [1, 2, 3]; > > auto valRange = ValRange(ints); > > > > writeln("Val 1st Run:"); > > foreach(i; valRange) writeln(i); > > assert(!valRange.empty); > > > > writeln("Val 2nd Run:"); > > foreach(i; valRange) writeln(i); > > assert(!valRange.empty); > > > > auto refRange = new RefRange(ints); > > > > writeln("Ref 1st Run:"); > > foreach(i; refRange) writeln(i); > > assert(refRange.empty); > > > > writeln("Ref 2nd Run:"); > > foreach(i; refRange) writeln(i); // prints nothing > > > > } > > Thanks for the reference and the code. I will have to iterate > over the packpub text a while consulting the docs. I see that the > code runs as you say, but I don't understand what's going on. You > say with regard to a "value type" : "you're consuming a copy of > the original range" but I don't see anything different between > the processing in the struct versus in the class. They both have > a dynamic array variable that they re-assign a "slice" to (or > maybe that is - that they modify to be the sliced version). > Anyway, I can't see why the one in the struct shrinks and then > goes back to what it was originally. It's like calls were made by > the compiler that aren't shown.
When you have foreach(e; range) it gets lowered to something like for(auto r = range; !r.empty; r.popFront()) { auto e = r.front; } So, the range is copied when you use it in a foreach. In the case of a class, it's just the reference that's copied. So, both "r" and "range" refer to the same object, but with a struct, you get two separate copies. So, when foreach iterates over "r", "range" isn't mutated. So, in the general case, if you want to use a range in foreach without consuming the range, it needs to be a forward range, and you need to call save. e.g. foreach(e; range.save) For many ranges, copying a range is equivalent to calling save, but for some it is not (most notably classes, since copying a class reference just means that you get two references to the same object). So, it's pretty typical for folks to write code that doesn't use save where it should and that works just fine with dynamic arrays and many structs but which fails miserably when you pass it a class. Also, just because something is a struct doesn't mean that copying it does a deep enough copy. If multiple variables hold state in the struct, and some of them are reference types and some are value types, then copying the struct does not result in an independent copy - and you can get really weird results when that happens. That's why it's important to test range-based functions with a variety of range types if it's intended to work with ranges in general as opposed to a specific type. Then you can ensure that you aren't accidentally relying on some aspect of a specific range type. - Jonathan M Davis