Difference between input range and forward range

Jonathan M Davis via Digitalmars-d digitalmars-d at puremagic.com
Tue Nov 10 08:06:59 PST 2015


On Tuesday, 10 November 2015 at 14:28:15 UTC, Shachar Shemesh 
wrote:
> Am I missing something?

The short answer is that input ranges need to be copyable to work 
with foreach, because of how foreach is defined for ranges. The 
long answer is...

Input ranges _are_ copyable. It's just that their state isn't 
guaranteed to be copied (and if it is copied, then it probably 
should have been a forward range anyway). The semantics of what 
happens when copying any type of range are undefined, because it 
depends on how the range is implemented. For example, if you have

auto copy = orig;
copy.popFront();

and the type of orig and copy is a reference type, then popping 
off an element from copy popped off an element from orig, whereas 
if the range is a value type, then popping off an element from 
copy has no effect on orig. And if the range is a 
pseudo-reference type, then it'll probably do something like pop 
off an element from orig but not affect orig's front, but it 
could also be that it has no effect on orig (what exactly happens 
depends on how the range is implemented). Basically, if you copy 
a range, you can't do _anything_ with the original unless you 
assign it a value, because what happens when you call anything on 
the original depends on how its implemented and thus is undefined 
behavior in general. I talked about this problem in my dconf 2015 
talk:

http://dconf.org/2015/talks/davis.html

Now, with regards to foreach specifically,

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

becomes

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

Notice that the range is copied. So, yes, for a range to work 
with foreach, it's going to have to be copyable. @disabling the 
postblit constructor is just going to cause you trouble. But the 
key thing here is that this means that once you use a range in a 
foreach loop, you can't use it for anything else. In generic 
code, you have to consider it to be consumed, because the state 
of range you passed to foreach is now undefined, since what 
happens when copying the range is undefined. This is true even if 
you put a break out of the loop, because the range was copied, 
and you simply cannot rely on the state of the range you passed 
to foreach after that copy.

Now, if you know the exact semantics of a particular range type, 
and the code you're writing is not generic, then you have more 
leeway, but in generic code, you have to be very careful to make 
sure that you don't use a range that has been copied unless it's 
been assigned a new value - and that includes not using a range 
after passing it to foreach. We're frequently lax with this due 
to the fact that most ranges are value types or pseudo-reference 
types that act like value types, so they're not only forward 
ranges, but they're implicitly saved by a copy, and so we 
frequently get away with using a range after it's been copied, 
but it doesn't work in the general case.

- Jonathan M Davis


More information about the Digitalmars-d mailing list