Mutable By-Value Capture in C++11
Here is a quirks I stumbled upon in modern C++. As an example we will be printing all odd numbers in a vector and the count of odd number so far. For the sake of demonstration I’ll be using the following method.
#include <iostream>
#include <vector>
#include <algorithm>
int main(int argc, char const *argv[])
{
auto numbers = std::vector<int>{0,1,2,3,4,5,6,7,8,9};
auto count = 0;
auto odd = [=] (int n) mutable {
if (n & 1) {
std::cout << n << " " << count << std::endl;
count++;
return true;
}
return false;
};
std::remove_if(std::begin(numbers), std::end(numbers), odd);
return 0;
}
The code might be a bit needlessly complicated, but it looks correct at first glance. However, the output is unexpected.
1 0
3 0
5 1
7 2
9 3
Somehow the first count++
didn’t execute? Well, it did. It is just that there
are two version of the lambda floating around, both with their own count.
Take a look at the source of remove_if
(here gcc’s version). In line 863
find_if
is called from within remove_if
. Note that both, remove_if
and find_if
take the predicate by-value. This means that the lambda is copied into find_if
.
However, the adapted count is not copied out, afterwards. So later the count
is still 0, when the main loop executes.
There are different ways to avoid this quirks. One is to use libc++ from the
LLVM project. It passes the lambda by-reference instead of by-value to find_if
.
Another, simpler way would be to capture by reference to begin with.
As the lambda gets copied, one could come up with more edgy cases even if the capture is non-mutable. However, that would require weird custom copy-constructors.
Note that this is indeed standard compliant behaviour. However, I still opened a bug against libstdc++. Let’s hope they come up with a good solution.