range.save

Jonathan M Davis via Digitalmars-d digitalmars-d at puremagic.com
Fri Nov 20 09:53:01 PST 2015


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


More information about the Digitalmars-d mailing list