Skip to main content

Mutable By-Value Capture in C++11

·306 words

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.