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

by Jeff Langr

March 06, 2013

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

More 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 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 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.

Share your comment

Jeff Langr

About the Author

Jeff Langr has been building software for 40 years and writing about it heavily for 20. You can find out more about Jeff, learn from the many helpful articles and books he's written, or read one of his 1000+ combined blog (including Agile in a Flash) and public posts.