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