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