I find the empty/front/popNext protocol very cumbersome to implement for most cases (at least in the Weka codebase), which causes people to just prefer opApply (even though it has its own set of issues).
Today I even ran into a problem that cannot be solved using the existing protocol: a consuming range (think a file stream) that should not consume the element if the foreach has been broken. Using opApply, this is easy (the delegate returns nonzero), but using popNext it's next to impossible (or at least really really cumbersome). I don't want to go into all the details, so let's just jump to the suggested protocol.
bool opNext(out T elem) {...}
opNext
returns true if it "produced" an element, false if it reached the end. That way
foreach (elem; range) {
...
}
Simply gets lowered to
T elem;
while (range.opNext(elem)) {
...
}
Of course this protocol can live side-by-side with the existing protocol, just take precedence in the lowering rules. A concrete example:
struct LinkedList {
int value;
LinkedList* next;
struct Range {
LinkedList* cursor;
bool opNext(out int val) nothrow @nogc {
if (cursor is null) {
return false;
}
val = cursor.value;
return true;
}
}
auto members() {return Range(&this);}
}
foreach (val; myList.members) {
...
}
Notes:
save()
can work for the new protocol as well, it just needs to duplicate the iterator's state- Maybe it would be wise to complement this with opPrev for bidirectional ranges, but I have to say I've rarely seen any use of them
- I think chaining with map/filter/etc. is even easier to implement in the new protocol