opApply & opApplyReverse Member Functions

Understand foreach support by opApply and opApplyReverse member functions.

foreach, opApply, and opApplyReverse

Everything that is said about opApply in this section is valid for opApplyReverse as well. opApplyReverse is for defining the behaviors of objects in the foreach_reverse loops.

The member functions above allow us to use objects as ranges. That method is more suitable when there is only one sensible way of iterating over a range. For example, it would be easy to provide access to individual students of a Students type.

On the other hand, sometimes it makes more sense to iterate over the same object in different ways. We know this from associative arrays, where it is possible to access either only to the values or to both the keys and the values:

string[string] dictionary;    // from English to Turkish

// ...

foreach (inTurkish; dictionary) {
    // ... only values ...
}
foreach (inEnglish, inTurkish; dictionary) {
    // ... keys and values ...
}

opApply allows using user-defined types with foreach in various and sometimes more complex ways. Before learning how to define opApply, you must first understand how it is called automatically by foreach.

The program execution alternates between the expressions inside the foreach block and the expressions inside the opApply() function. First the opApply() member function gets called, and then opApply makes an explicit call to the foreach block. They alternate in that way until the loop eventually terminates. This process is based on a convention, which we will explain soon. Let’s first observe the structure of the foreach loop one more time:

// The loop that is written by the programmer:

foreach (/* loop variables */; myObject) {
    // ... expressions inside the foreach block ...
}

If there is an opApply() member function that matches the loop variables, then the foreach block becomes a delegate, which is then passed to opApply().

Accordingly, the loop above is converted to the following code behind the scenes. The curly brackets that define the body of the delegate are highlighted:

// The code that the compiler generates behind the scenes:
myObject.opApply(delegate int(/* loop variables */) { 
    // ... expressions inside the foreach block ...
        return hasBeenTerminated;
});

In other words, the foreach loop is replaced by a delegate that is passed to opApply(). Before showing an example, here are the requirements and expectations of this convention that opApply() must observe:

  1. The body of the foreach loop becomes the body of the delegate. opApply must call this delegate for each iteration.

  2. The loop variables become the parameters of the delegate. opApply() must define these parameters as ref.

  3. The return type of the delegate is int. Accordingly, the compiler injects a return statement at the end of the delegate, which determines whether the loop has been terminated (by a break or a return statement. If the return value is zero, the iteration must continue; otherwise, it must terminate.

  4. The actual iteration happens inside opApply().

  5. opApply() must return the same value that is returned by the delegate.

The following is a definition of NumberRange that is implemented according to that convention:

Get hands-on with 1000+ tech skills courses.