2

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 statics, 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.

2 Answers 2

6

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.

Sign up to request clarification or add additional context in comments.

1 Comment

"A lambda's type is defined at the point where the lambda is created" Identifying that part is not as trivial though, Demo
6

In the case of static_lambda, there is only ever one i since it is declared as a function local static variable. Basically it stamps out a class like

class _lambda_
{
public:
    operator()() { static int i = 0; return i++; }
};

As you can see, any time the operator() of the lambda object is called the same i is used every time.

In the case of mutable lambda, you basically return a instance of a class that looks like

class _lambda_
{
    int i = 0;
public:
    operator()() { return i++; }
};

and in this case each time you call mutable_lambda you get a copy of that default constructed class. That means m and m2, each have their own i and modifying one wont modify the other.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.