On Tuesday, 24 August 2021 at 08:36:18 UTC, frame wrote:
Consider a simple input range that can be iterated with
empty(), front() and popFront(). That is comfortable to use
with foreach() but what if the foreach loop will be cancelled?
If a range isn't depleted yet and continued it will supply the
same data twice on front() in the next use of foreach().
For some reason, foreach() does not call popFront() on a break
or continue statement. There is no way to detect it except the
range itself tracks its status and does an implicit popFront()
if needed - but then this whole interface is some kind of
useless.
There is opApply() on the other hand that is designed for
foreach() and informs via non-0-result if the loop is cancelled
- but this means that every range must implement it if the
range should work in foreach() correctly?
This is very inconsistent. Either foreach() should deny usage
of ranges that have no opApply() method or there should be a
reset() or cancel() method in the interfaces that may be called
by foreach() if they are implemented.
How do you handle that issue? Are your ranges designed to have
this bug or do you implement opApply() always?
A range should be a struct always and thus its state is copied
when the foreach loop is created.
Which means the state resets every time the loop is initiated.
If your range uses some internal state that isn't able to be
copied then or your ranges are not structs then your ranges are
inherently incorrect.
This is what a foreach loop on a range actually compiles to:
```d
for (auto copy = range; !copy.empty; copy.popFront())
{
...
}
```
This is easily evident in this example:
https://run.dlang.io/is/YFuWHn
Which prints:
1
2
1
2
3
4
5
Unless I'm misunderstanding your concern?