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.