The solution posted by the OP is a valid solution, but in D2 there's another one with a different set of tradeoffs. Iteration in D can be divided into internal iteration, which is handled by opApply, and external iteration, which is handled by ranges.
Internal iteration gives the object being iterated over control of the call stack. This allows, for example, recursion to be used by the object being iterated over without maintaining an explicit stack, but makes iterating over multiple structures in lockstep impossible. External iteration does the opposite.
External iteration is accomplished via ranges. A range is any class or struct that defines three methods: front()
gives access to the first element in the range, popFront()
advances the range and empty()
returns true
if the range is empty. If the range is infinite, empty can be declared as a constant instead of a member function. In this way, the caller has control over the call stack, which is a tradeoff that can be good or bad depending on the situation.
Here's an example of using ranges for iteration:
/**This struct lazily produces all Fibonacci numbers.*/
struct Fibonacci {
ulong num1 = 0;
ulong num2 = 1;
ulong front() {
return num1 + num2;
}
void popFront() {
auto newNum2 = num1 + num2;
num1 = num2;
num2 = newNum2;
}
// A range of Fibonacci numbers is infinite.
enum bool empty = false;
}