17
\$\begingroup\$

Is using a static variable in a lambda function ok, or considered a bad practice? The code below works as intended (fills a vector with consecutive numbers).

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main()
{
    vector<int> vec(100);

    generate(vec.begin(), vec.end(), [] () { static int i = 0; return i++; });
}
\$\endgroup\$
  • 5
    \$\begingroup\$ @Avery, C++ is great in that if something appears to work, it still might not always work. That said, this example should just use std::iota. \$\endgroup\$ – chris Jul 27 '14 at 16:24
  • \$\begingroup\$ @chris True, I was reading his question as a request to review his code - not as a question of future support (which I'm still not sure that he's asking about). \$\endgroup\$ – Avery Jul 27 '14 at 16:29
  • \$\begingroup\$ @Avery - I asked about possible corner cases of using a global variable in lambda functions. \$\endgroup\$ – w.b Jul 27 '14 at 16:47
  • \$\begingroup\$ Please edit your question so that the title describes the purpose of the code, rather than its mechanism. We really need to understand the motivational context to give good reviews. Thanks! \$\endgroup\$ – Toby Speight Mar 9 at 15:13
  • 1
    \$\begingroup\$ @TobySpeight The question was asked and answered in '14. \$\endgroup\$ – Mast Mar 9 at 21:07
15
\$\begingroup\$

Yes it is perfectly valid.

lambdas in C++ were designed to be functionally equivalent to functors that were used a lot in C++03.

So you can consider:

auto x = [state1, state2](Param1 param1, Param2 param2){/* Do Stuff */};

To be functionally equivalent to:

struct AnonClassX
{
    State1  state1;
    State2  state2;
    AnonClassX(State1 state1, State2 state2)
       : state1(state1)
       , state2(state2)
    {}
    returnValue operator()(Param1 param1, Param2 param2) const
    {
         /* Do Stuff */
    }
};
AnonClassX   x(state1,state2);

It is quite normal to use static variables in functions and methods. Lambda is just a shorthand for creating an anonymous class with state and an operator()() to make it act like a function. So it should be very normal to put static members inside it.

\$\endgroup\$
  • 1
    \$\begingroup\$ Hmm, it feels to me that this is not really an answer to the question. Your examples lack static variables, and I don't think that static variables in functions are everyday occurences. \$\endgroup\$ – phresnel Jul 28 '14 at 13:59
  • 1
    \$\begingroup\$ @phresnel: The example is not meant to show the usage of static. It is supposed to show the equivelence of lambda to functor. Then the last paragraph answers the question. Static function variables are a tool. They are used a lot. But like all tools they are subject to abuse. You have to look at their use in context. \$\endgroup\$ – Martin York Jul 28 '14 at 14:37
  • 1
    \$\begingroup\$ @phresnel: Q1: Is the use in lambda OK: A1: Yes. \$\endgroup\$ – Martin York Jul 28 '14 at 14:37
  • 1
    \$\begingroup\$ @phresnel: Q2: or considered a bad practice: A2: It is no more bad practice than usage in a functor. You can not answer than in the general case. In this specific case I see no issue as the intent is clear. \$\endgroup\$ – Martin York Jul 28 '14 at 14:39
  • 1
    \$\begingroup\$ @jliv902: As I have already explained its not supposed to. In my answer I am comparing the difference between a lambda and a functor to show their equivalence. I then note (in the last paragraph): it is normal to use function static objects in methods so it should be just as normal to use them in lambda. In the comments I then also explain to phresnel that it is impossible to answer or considered a bad practice in the general case (it is just as good or bad practice as using it in a functor). In this specific case I have no issues as its intent is clear. \$\endgroup\$ – Martin York Jul 28 '14 at 16:18
14
\$\begingroup\$

I wouldn't accept it in most of cases. Because if you do this:

vector<int> giveme() {
    vector<int> vec(100);

    generate(vec.begin(), vec.end(), [] () { static int i = 0; return i++; });
    return vec;
}

int main()
{
    auto a = giveme();
    auto b = giveme();
}

b would contain different values than a, and in my opinion, this is very far from obvious for the reader.

\$\endgroup\$
  • 3
    \$\begingroup\$ That's more of a reflection on the function name giveme() than anything else. Self documenting names should help with intent. giveMeNext100ElementsOfIdSequence(). \$\endgroup\$ – Martin York Jul 28 '14 at 14:44
  • \$\begingroup\$ Wut. To prove a different point, you wrapped the lambda in a very badly named function then complained about the latter. Which wasn't what the OP was asking at all. The risk of people using rubbish names, in this case that don't indicate they rely on static variables, and being surprised by the result, is no different with a lambda than any other construct That's not an argument that it's bad. It's an argument that your made-up wrapping function that nobody asked about is badly named, and hence that this answer has almost no relevance. \$\endgroup\$ – underscore_d Aug 16 '16 at 17:39
  • \$\begingroup\$ When writing code you want to minimize sources of confusion. You want to think in clear names and in clear declarations among other things. If you fail to get right one of these things, code looks bad. If you fail to get right two of these things at the same time, code looks much worse. Confusion is in fact accumulative. And programmers do mistakes all the time. So better target to make everything clear (not just names), so when you do a mistake in one thing, the situation is still not as bad as it would be when you usually hide static declarations inside lambdas by default, and similar. \$\endgroup\$ – José Manuel Aug 18 '16 at 8:05
9
\$\begingroup\$

Your code is fine. However, in this case I would use a closure to make the intended purpose more obvious to the reader:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main()
{
    vector<int> vec(100);

    int i = 0;
    generate(vec.begin(), vec.end(), [&i]() { return i++; });

    return 0;
}
\$\endgroup\$
  • \$\begingroup\$ Sorry but what these circumstances are exactly? \$\endgroup\$ – c-smile Jul 27 '14 at 16:32
  • \$\begingroup\$ The usage of a static variable inside a lambda expression... \$\endgroup\$ – Stefano Sanfilippo Jul 27 '14 at 16:33
  • 3
    \$\begingroup\$ For one the lifetime of any function scope static is until end of main, so unless the compiler realizes the static can never be reached after the first use you'll still pay for it after returning. \$\endgroup\$ – Ylisar Jul 27 '14 at 17:11
  • 1
    \$\begingroup\$ "Still, I tend to avoid local static variables whenever possible, since they make the functions stateful." -- isn't that the whole point of using a static variable in the first place? \$\endgroup\$ – KutuluMike Jul 28 '14 at 14:22
  • 1
    \$\begingroup\$ @MichaelEdenfield yes, and that's the reason I don't usually use them (assuming you mean static local, which is not static global). Functions should be as close to mathematical functions as possible, in the sense that consecutive calls with same parameters yield the same result. Once you retain an internal state between calls, you loose this good property, making debugging/testing harder and multithreading a hell. You have no way to cleanly manipulate a static local from outside the function, so in the rare cases where you really need a stateful function, you should be using a functor. \$\endgroup\$ – Stefano Sanfilippo Jul 28 '14 at 14:52
6
\$\begingroup\$

That's exactly what static variables are for - to keep some state between function calls.

\$\endgroup\$
5
\$\begingroup\$

Use of static in a lambda is okay, but it might not be what you want.

If you use static, the variable is persistent over lambda instantiations. If the behaviour you want is instead equivalent to dependency injection, you may consider using the mutable keyword.

The following code illustrates the difference:

void foo() {
   auto f = [k=int(1)]() mutable { cout << k++ << "\n";}; // define k in the capture
   f();
   f();
}

void bar() {
   auto f = []() { static int k = 1; cout << k++ << "\n";}; // define k as static
   f();
   f();
}

void test() {
   foo();
   foo();  // k is reset every time the lambda is created
   bar();
   bar();  // k is persistent through lambda instantiations
}
\$\endgroup\$
4
\$\begingroup\$

This answer is late to the party, but I thought this needed to be said:

In most cases when you think static is a good idea for shared state, you should at least consider dependency injection instead.

In other words:

Is using a static variable in a lambda function ok, or considered a bad practice?

It is perfectly valid C++, but consider using an injected seed instead:

int main()
{
    vector<int> vec(100);

    int count = 0;
    // look ma', no static!
    generate(vec.begin(), vec.end(), [&count] () { return count++; });
}

In this (simplistic) example, the two are functionally equivalent. In practice though, static shares state between calls, even when you don't want it to (e.g. you may want to call this from multiple threads in the future, each with it's own count).

Both are acceptable and correct C++ (YMMV), but the [&count] alternative leads to less (hidden) side effects, more testable code, more re-usable code and a better habbit to cultivate in written code.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.