Should range foreach be iterating over an implicit copy?
Nick Sabalausky
SeeWebsiteToContactMe at semitwist.com
Wed May 16 14:37:14 PDT 2012
A small debate has broken out over on D.learn (
http://forum.dlang.org/thread/jovicg$jta$1@digitalmars.com#post-jovicg:24jta:241:40digitalmars.com )
that I thought I should move here.
Basically, the issue is this: Currently, when you have a struct-based range,
foreach will iterate over a *copy* of it:
Range r;
auto save = r;
foreach(e; r)
assert(r == save);
assert(r == save);
One side of the argument is that this behavior is correct and expected since
structs are value types, and iterating shouldn't consume the range.
My argument is this:
First of all, foreach is conceptually a flow-control statement, not a
function. So I'd intuitively expect:
Range r;
foreach(e; r) {...}
To work like this:
Range r;
for(; !r.empty; r.popFront)
{
auto e = r.front;
...
}
Additionally, iterating a range normally *does* consume it. And that's
expected, as it should be: Imagine, for example, an input range that read
from stdin. Leaving that range unconsumed would make no sense at all.
Actually, any input range can be expected to *not* leave an uniterated copy
behind: if it *could* have an uniterated copy left behind, it would be a
forward range, not an input range.
It actually seems wrong to use the current foreach over an input range:
Sometimes it might work by pure luck (as in the original example), but you
can expect that for some input ranges it would fail miserably, because an
input range is *not* a forward range and by definition cannot reliably save
a previous state. So iterating over an input range would leave the original
input range in an undefined state (actually, that could even be true for
certain forward ranges if foreach doesn't implicitly start out by calling
"r.save" and the range doesn't expect to be saved by copying).
Suppose foreach *didn't* iterate over a copy: If you wanted to still have an
un-iterated version (of an actual forward range, or an input range that you
knew to be safe), then that's trivial:
Foo a;
b = a.save(); // Or "b = a;" if you really know what you're doing.
foreach(elem; a) {}
assert(a.empty);
assert(!b.empty);
Note, however, that there is no such simple way to go the other way around:
to have the current "foreach over a copy" behavior and have access to the
actual iterated range. Maybe if we had "foreach(e; ref range)", but AFAIK we
don't.
One counter-argument that was raised is that TDPL has an example on page 381
that indicates foreach iterates over an implicit copy. I don't have a copy
handy ATM, so I can't look at it myself, but I'd love to see Andrei weigh in
on this: I'm curious if this example in TDPL made that copy deliberately, or
if the full implications of that copy were just an oversight.
More information about the Digitalmars-d
mailing list