User defined type and foreach

Jonathan M Davis newsgroup.d at jmdavisprog.com
Fri Nov 17 17:55:30 UTC 2017


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



More information about the Digitalmars-d-learn mailing list