C++11: Using Lambdas to Support a Times-Repeat Loop

lambdaMore often than not, explicit for loops that execute n times usually need to know what what n is. Sometimes they don’t, in which case the for-loop structure seems mildly tedious. It’s certainly idiomatic, and you’ve seen ’em all a million times:

for (auto i = 0; i < 5; i++) {
   // code to repeat here
}

Still, having seen them all a million times, you’ve encountered many either accidental or purposeful variations:

for (auto i = 1; i < 5; i++) {
   // code to repeat here
}
for (auto i = 0; i <= 5; i++) {
   // code to repeat here
}
for (auto i = 5; i >= 0; i++) {
   // code to repeat here
}
for (auto i = 0; i < 5; i += 2) {
   // code to repeat here
}

The result: A for loop is something you must always pause to inspect. It’s too easy for your brain to miss one of these variants.

I’d much rather be able to simply send a timesRepeat message to an integer, as I can do in Smalltalk (Ruby has a similar message, times):

   5 timesRepeat: [ "code to repeat here" ]

In C++, overloading the multiplication operator would allow me to code something like:

   5 * [] { /* code to repeat here */ }

(The [] is the capture clause, which indicates that the following expression is a lambda, and also specifies how variables might be captured. In this case, the empty capture clause [] indicates that no variables are captured.)

A lambda function is of type function, therefore I can support the times-repeat expression with the following:

int operator*(int number, function<void (void)> func) {
   for (int i = 0; i < number; i++) func();
}

In other words, simply translate the lambda into the corresponding for loop, iterating number times, and calling func() each time, where number represents the receiver (5 in the example usage above).

A simple test demonstrates that the construct works:

unsigned int totalGlobal;

TEST(Lambdas, CanSupportedASimplifiedTimesRepeatLoop) {
   5 * [] { totalGlobal++; };

   ASSERT_THAT(totalGlobal, Eq(5));
}

(Use of the global variable totalGlobal allows writing the lambda expression without requiring variable capture.)

The resulting expression is succinct and even better than idiomatic, it’s obvious. I don’t think I’d go as far as to say that you should replace all for loops, just ones where access to the index is not required.

You could, of course. Here’s the result, a couple small tweaks to the first solution.

int operator*(int number, function<void (int)> func) {
   for (int i = 0; i < number; i++) func(i);
}

TEST(Lambdas, CanUnnecessarilyReplaceAForLoop) {
   unsigned int total{0};

   4 * [&] (int i) { total += i; };

   ASSERT_THAT(total, Eq(0 + 1 + 2 + 3));
}

The operator overload now simply passes the loop index i to the function being executed.

In the test, I now choose to use a local variable to capture the total. The capture clause of [&] indicates that any variable accessed by the lambda function is captured by reference. The function now also includes a parameter list, specifying in this case the index parameter of i.

I doubt I’d promote use of the second form (lambda with indexer) in production code. The first (times repeat) seems succinct and appropriate.

Leave a Reply

*

captcha *

Atom