Often it is desirable for a lambda function to read or modify state which has to be preserved between calls. Just about the simplest example of a stateful lambda is one that adds up all the numbers it is called with, as shown in this example program:
#include <algorithm>
#include <iostream>
int main() {
auto data = { 1.0, 2.2, 3.1, 2.1, 0.5, 1.6 };
double sum{};
auto sum_and_average = [&](const auto& elem){
std::cout << elem << " ";
sum += elem;
};
std::for_each(begin(data), end(data), sum_and_average);
std::cout << "\nSum: " << sum << '\n';
}
Before you shout “Couldn’t we have used std::accumulate()
instead!” let’s look at the output from this program:
1 2.2 3.1 2.1 0.5 1.6
Sum: 10.5
(The facility of outputting each number from the iterations performed by std::for_each
is not available when using std::accumulate
.)
As can be seen from this code, the lambda accesses the variable sum
from its immediately enclosing scope (as well as std::cout
, which is of course a global object). The facility which allows this to happen is the use of [&]
when defining the lambda, this being shorthand for “capture all by reference”. (If we wanted to, we could name all of the variable(s) to be “captured” in this way explicitly, but this probably won’t make the code any more efficient.) Should you omit the &
(as for previous stateless lambdas), or even use [=]
(“capture all by value”) instead, the compiler will complain with an error message.
However we’re still not done, as you may have guessed from the name of the lambda. To keep a running average, which we’ll examine half way through the run, some changes need to be made, as shown below:
#include <algorithm>
#include <iostream>
int main() {
auto data = { 1.0, 2.2, 3.1, 2.1, 0.5, 1.6 };
double sum{}, average{};
unsigned count{};
auto sum_and_average = [&](const auto& elem){
std::cout << elem << " ";
++count;
sum += elem;
average = sum / count;
};
std::for_each(begin(data), begin(data) + size(data) / 2, sum_and_average);
std::cout << "\nSum (1/2): " << sum << ", Average (1/2): " << average << '\n';
std::for_each(begin(data) + size(data) / 2, end(data), sum_and_average);
std::cout << "\nSum: " << sum << ", Average: " << average << '\n';
}
This program generates the following output (with the final Sum as before):
1 2.2 3.1
Sum (1/2): 6.3, Average (1/2): 2.1
2.1 0.5 1.6
Sum: 10.5, Average: 1.75
It may be useful to draw an analogy between stateful lambdas and functors with member variables, however this isn’t necessarily the most accurate way of comparing them. Let’s take a look at the first version rewritten as a functor called Sum
:
struct Sum {
double sum{};
template<typename T>
void operator() (const T& elem) {
std::cout << elem << " ";
sum += elem;
}
};
// ...
Sum s;
std::for_each(begin(data), end(data), std::ref(s));
std::cout << "\nSum: " << s.sum << '\n';
From our lowering examples in the previous articles this code should be fairly obvious. There needs to be a use of std::ref(s)
and not Sum{}
as we need the object to both outlive the scope of the std::for_each()
loop, and not itself be a copy.
However a more authentic rewrite-as-a-functor would use a member variable which is a reference:
struct Sum {
double& d_sum;
Sum(double &s) : d_sum{ s } {}
template<typename T>
void operator() (const T& elem) {
std::cout << elem << " ";
d_sum += elem;
}
};
// ...
double sum{};
Sum s(sum);
std::for_each(begin(data), end(data), s);
Notice that we need to have sum
as a local variable again, and no longer need std::ref(s)
, plain s
will do.
In the next article of this mini-series we’ll take a look at by value [=]
capture, and why the mutable
keyword is required when modifying variables captured in this way, as well as starting a discussion about returning stateful lambdas from functions.