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