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

Reply via email to