On Friday, 17 November 2017 at 17:55:30 UTC, Jonathan M Davis wrote:

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.

Ah, I get it now ("r=range; process r"), thanks!


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)


Seems like you can make class-based ranges to work on multiple foreach calls without having to do save, although maybe it falls apart in other usage. It also doesn't appear that the compiler requires an @property annotation as specified in the interface :

import std.stdio : writeln;

class RefRange {
    int foreach_index;
    int[] items;
    this(int[] src)
    {
       items = src;
    }

    bool empty()
    {
       if (foreach_index == items.length)
       {
          foreach_index = 0; // reset for another foreach
          return true;
       }
       return false;
    }
    int front() { return items[foreach_index]; }
    void popFront() { foreach_index++; }
}

void main() {
    import std.stdio;

    int[] ints = [1, 2, 3];
    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); // works
}
------------------------------------------
Ref 1st Run:
1
2
3
Ref 2nd Run:
1
2
3

Reply via email to