'std::algorithm functions lambda capture called several times
As far as I know, lambda capture variable lifecycle is bound to lifecycle of lamda object. For example, in this case:
#include <string>
#include <vector>
using namespace std;
class SomeCla {
public:
constexpr SomeCla(int i, float f) noexcept : _i(i), _f(f) {}
~SomeCla() {
puts("dtor");
}
SomeCla(const SomeCla&) = default;
SomeCla(SomeCla&&) = default;
SomeCla& operator=(const SomeCla&) = default;
SomeCla& operator=(SomeCla&&) = default;
constexpr float total() const noexcept { return static_cast<float>(_i) + _f; }
private:
int _i;
float _f;
};
int main() {
vector<float> vec = { 1.0f };
const auto filler = [someCla = SomeCla(1, 2.0f)](vector<float>& someVec, int val) {
someVec.push_back(someCla.total() + static_cast<float>(val));
};
for (int i = 0; i < 10; ++i) {
filler(vec, i * 3);
}
return static_cast<int>(vec.size());
}
Output is:
dtor
"dtor" was put only once even if we're calling lamda several times, which is expected.
But there are strange things are about std::algorithm functions. If we use them:
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
class SomeCla {
public:
constexpr SomeCla(int i, float f) noexcept : _i(i), _f(f) {}
~SomeCla() {
puts("dtor");
}
SomeCla(const SomeCla&) = default;
SomeCla(SomeCla&&) = default;
SomeCla& operator=(const SomeCla&) = default;
SomeCla& operator=(SomeCla&&) = default;
constexpr float total() const noexcept { return static_cast<float>(_i) + _f; }
private:
int _i;
float _f;
};
int main() {
vector<float> vec = { 1.0f };
erase_if(vec, [someCla = SomeCla(1, 2.0f)](float ele) {
return ele == someCla.total();
});
puts("continue");
{
const auto filler = [someCla = SomeCla(1, 2.0f)](vector<float>& someVec, int val) {
someVec.push_back(someCla.total() + static_cast<float>(val));
};
for (int i = 0; i < 10; ++i) {
filler(vec, i * 3);
}
}
puts("continue2");
ignore = none_of(vec.cbegin(), vec.cend(), [someCla = SomeCla(1, 2.0f)](float ele) { return ele == -1.0f; });
puts("heyyyyyyyyyyyyy");
ignore = any_of(vec.cbegin(), vec.cend(), [someCla = SomeCla(1, 2.0f)](float ele) { return ele == 1.0f; });
return static_cast<int>(vec.size());
}
Output will be like:
dtor
dtor
dtor
dtor
dtor
dtor
dtor
continue
dtor
continue2
dtor
dtor
dtor
dtor
dtor
dtor
heyyyyyyyyyyyyy
dtor
dtor
dtor
dtor
dtor
dtor
dtor
7 "dtor" on std::erase_if, 6 "dtor" on std::none_of, 7 "dtor" on std::any_of and only 1 "dtor" on normal call of lambda (as expected). And these numbers are independent from container size. I tried and got same numbers.
So, the question is, is it a bug or dependent on implementation details of std::algorithm functions? It seems, these std::algorithm functions probably constructs and destructs lamda object several times, that's the why our lambda capture variable was constructed and destructed several times.
By the way, another strange thing is these numbers (which is 2) on MSVC build are lower than GCC and Clang but still more than 1. Here is MSVC output:
dtor
dtor
continue
dtor
continue2
dtor
dtor
heyyyyyyyyyyyyy
dtor
dtor
Here it can be tested: https://godbolt.org/z/nWd77c9o6
For now, I decided not to create lambda capture variables (if they are not basic types) on calling std::algorithm functions, I'll create the variable and pass by reference on lambda capture instead.
Solution 1:[1]
Just capture an instance of SomeCla by reference in the closure of your lambdas, ie:
SomeCla someCla(1,2.0f);
erase_if(vec, [&someCla](float ele) {
return ele == someCla.total();
});
Changing this everywhere gave me:
continue
continue2
heyyyyyyyyyyyyy
dtor
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Jean-Baptiste Yunès |
