On Friday, 20 November 2015 at 16:35:01 UTC, Sönke Ludwig wrote:
The lowering of "foreach" to "for" would also have to be adjusted accordingly.

I'm actually wondering if we should change it even to support the way that we currently do things. The problem is that the semantics of copying a range are undefined. They depend entirely on how the range is implemented (value types, reference types, and pseudo-reference types all act differently when copied). The result is that if you want generic range-based code to work with all range types, you can never use a range after it's been copied unless it's assigned a new value - and foreach does a copy!

foreach(e; range)
{
    // do stuff
}

->

for(auto __c = range; !__c.empty; __c.popFront())
{
    auto e = __c.front;
    // do stuff
}

So, in generic code, once you use foreach on a range, you can't use it again. Something like

foreach(e; range)
{
   if(cond)
       break;
}
// do something with range again

is inherently broken. You're pretty much forced to use a naked for loop, e.g.

for(; !range.empty; range.popFront())
{
    auto e = range.front;
    if(cond)
        break;
}
// do something with range

So, what we really want is probably something more like

foreach(e; range)
{
    // do stuff
}

->

for(; !range.empty; range.popFront())
{
    auto e = range.front;
    // do stuff
}

Unfortunately, that _does_ risk breaking some code with forward ranges - but only because they're being implicitly saved, which is arguably a bug. So, I'd be inclined to argue that the change should be made - if nothing else, because any attempts to break out of the loop are screwed with the currently lowering, and pure input ranges can't be saved to work around the problem, meaning that they're doubly screwed.

Now, that does have the downside of making foreach work differently for arrays, since they don't use the normal range lowering (rather, they're effectively saved), which means that we're still pretty much screwed...

So, maybe what we need to do is have the foreach lowering check for save and just call it if it exists so that pure input ranges get

for(; !range.empty; range.popFront())
{
    auto e = range.front;
    // do stuff
}

whereas forward ranges get

for(auto __c = range.save; !__c.empty; __c.popFront())
{
    auto e = __c.front;
    // do stuff
}

That's more complicated, but it avoids breaking code while still making it so that input ranges and foreach aren't quite so broken - though it means that input ranges and forward ranges act differently with foreach. But the alternative is basically tell folks that they need to call save explicitly when using foreach on forward ranges in generic code and to tell them that they should never use foreach on pure input ranges unless they intend to iterate over the whole range.

save tries to solve the copying problem with ranges, but the overall situation is _far_ worse than just trying to make it so that reference type ranges have a way to be copied properly.

- Jonathan M Davis

Reply via email to