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