I have encountered a lambda with static
variable in the following answer https://stackoverflow.com/a/79631994/2894535 :
EXPECT_CALL(my_mock, foo(_))
.WillRepeatedly(InvokeWithoutArgs([](){
static int counter = 0;
constexpr static std::array<int, 2> values {13, 26};
return values[counter++ % values.size()];
}) );
My first intuition was to comment or edit the answer with a mutable counter instead:
[counter = 0]() mutable { ... }
My logic was that the static
one could be unexpectedly shared between different users. For example, if the test was parametrized and the first instantiation called my_mock.foo()
odd number of times, then the second one will start from 26
instead of 13
.
On the second thought though, since each lambda creates an anonymous functor class, is that actually the case? I believe my confusion boils down to whether entering the same code twice will create a distinct functor with its separate static
s, or reuse them.
This can be boiled down to the minimal example:
#include <cstdio>
auto static_lambda() {
return []() { static int i = 0; return i++; }
}
auto mutable_lambda() {
return [i = 0]() mutable { return i++; }
}
int main() {
auto s = static_lambda();
std::printf("%d,", s());
std::printf("%d,", s());
auto s2 = static_lambda();
std::printf("%d,", s2());
std::printf("%d,", s2());
auto m = mutable_lambda();
std::printf("%d,", m());
std::printf("%d,", m());
auto m2 = mutable_lambda();
std::printf("%d,", m2());
std::printf("%d,", m2());
}
I have run this code and see it produces 0,1,2,3,0,1,0,1,
meaning my initial intuition was correct, but am honestly still not sure why, for the language lawyer point of view.
A lambda's type is defined at the point where the lambda is created. So, there is only 1 lambda type defined by static_lambda()
, and 1 lambda type defined by mutable_lambda()
, although multiple instances of those types are created at runtime.
Your code basically acts as-if you had written it something like this:
#include <cstdio>
auto static_lambda() {
class _lambda_123 {
public:
int operator()() const {
static int i = 0;
return i++;
}
};
return _lambda_123{};
}
auto mutable_lambda() {
class _lambda_456 {
int i = 0;
public:
int operator()() {
return i++;
}
};
return _lambda_456{};
}
int main() {
auto s = static_lambda();
std::printf("%d,", s.operator()()); // acts on _lambda_123::operator()::i
std::printf("%d,", s.operator()()); // acts on _lambda_123::operator()::i
auto s2 = static_lambda();
std::printf("%d,", s2.operator()()); // acts on _lambda_123::operator()::i
std::printf("%d,", s2.operator()()); // acts on _lambda_123::operator()::i
auto m = mutable_lambda();
std::printf("%d,", m.operator()()); // acts on m.i
std::printf("%d,", m.operator()()); // acts on m.i
auto m2 = mutable_lambda();
std::printf("%d,", m2.operator()()); // acts on m2.i
std::printf("%d,", m2.operator()()); // acts on m2.i
}
Like any other static
variable declared in any function, all invocations of the static_lambda
's function-call operator will act on a single static i
variable in memory, whereas all instances of the mutable_lambda
will capture a local copy of i
for its function-call operator to act on.