Is it ok to use lambda just locally? [closed]
Asked Answered
K

5

11

Is it alright to define a lambda function to be used only locally, that is in the current block (function/method)? Consider the situation when some code is going to be executed several times (so it'd be logical to put it into a function) but it will never be used outside the block.

void foo() {
    auto bar = []() {
        // some code applicable only inside foo()
    };

    bar();
    bar();
    bar();
}

What are the advantages and disadvantages of this approach, comparing to having bar() declared as normal function?

Kareykari answered 6/6, 2018 at 9:41 Comment(7)
I would extract bar into a separate function so it would be possible to properly test it.Sitra
@VTT: I never write my code in a way that anticipates the testing framework. Perhaps that's dogma, and explains why my testers don't like me very much.Baggs
Yes, using this in many situations results in less code.Horologe
I like to do so because it avoids (a) code repetition inside a function, or (b) a class interface having a plethora of private member functions that are only ever called from one function.Sienese
@Baggs - I also don't write code that anticipates the tests; it's much more effective to write a test before the code that implements it!Vinery
@TobySpeight: You're hired! Yes those folk who genuinely do develop in the unit test framework are divinities.Baggs
the function under test would be foo(). bar is the implementation detail of how foo was written to pass the tests.Kele
B
16

If the option to do so is available then yes, do localise the lambda bar to the particular function.

In C++, we always try to keep objects as local as possible. (Makes code easier to follow, simplifies documentation, facilitates refactoring, &c. &c..)

Baggs answered 6/6, 2018 at 9:42 Comment(17)
@Quentin: Better now or shall I reach for the coffee pot?Baggs
@Baggs : regarding disadvantages of this approach. Do you have any opinion? I am just curious.Uroscopy
@JeJo: It does give the testers a headache. But that's the tail wagging the dog. Lots of clever things have grown up in the testing world, such as template tricks for accessing private members in a portable way (there's an answer of mine on this exact topic but I can't find it). So something will emerge for buried lambdas that require their own testing. If we don't push the testers, they slack!Baggs
Now this is what I call "opinion-based answer"... "keep objects as local as possible" does not imply implementing classes and functions as close as possible. Other statements are rather controversial too, for example I would not call stack filled with lambda calls gibberish "easier to follow".Sitra
@Baggs "If the option to do so is available.." When is it not available?Refugee
@VTT: Surely that's because they are a new thing? We've all gotten used to templates. But I agree to an extent on your thinking this is an opinion, and a dogmatic one at that. I'm surprised nobody has downvoted yet...Baggs
@Baggs So you mean, in this situation there is no drawbacks by keeping as simple as it is. Right?Uroscopy
@JeJo, aside from you having to get the lunch in for your testing team, there are no drawbacks.Baggs
Some interesting thoughts in dimm's answer... I think it is a good option to avoid code duplication, if bar is tightly coupled to foo anyway (possibly even some local variables in closure). If bar could be used independently (even if it is not), I'd prefer a separate function (-> reusability).Markhor
When I localise a function I'm saying "I reserve the right to change that function without having to recompile and re-test the whole damn project". To that end I do disagree with the if "it could be reused" idealism.Baggs
@CinCout: The option is not really available if you have to repeat the code elsewhere; i.e. if it needs to be localised in more than one place. An example being a case-insensitive comparator for a std::map keyed by a std::string.Baggs
@Baggs I consider recompiling and retesting "the whole damn project" (along with all the projects that depend on it) after every change a normal practice.Sitra
"In C++, we always try to keep objects as local as possible." - why in C++ in particular? I think this is just common sense in any language.Chinaware
@Malcolm: Older languages don't. E.g. early formulations of C which required all the automatic variables to be declared at the top of the function. Didn't FORTRAN 77 require you to do that too?Baggs
@Baggs But you still wouldn't make local variables global if you could help it, would you? Obviously the limits the language imposes on you have to be obeyed, but the general rule remains.Chinaware
@Malcolm: Indeed, I agree completely. Perhaps I should have said "In C++, and indeed in any language if possible"? When I started out though, C was just emerging from K & R C, so the idea of localising things was pretty new from the point of view of C anyway.Baggs
@Baggs I would just omit "in C++". I'm just saying, I didn't mean that your answer is wrong or something.Chinaware
T
11

I agree that this is good practice. High level logic is almost always easier to read than low level logic. I often write functions in this way:

void foo()
{
  // introduce actors here
  auto something = make_something();

  // introduce detailed definitions
  auto bing = [&]()->bool { ... };  // define what binging means

  auto bong = [&]() { ... };  // define what bonging means

  // perform logic
  while (bing())
    bong();
}

Remember that as of c++17, lambdas are constexpr by default. gcc et. al. often optimise them away completely. None of this is a performance consideration.

Tullusus answered 6/6, 2018 at 9:56 Comment(0)
P
5

You can do it. Main question is will it be more readable? Putting it outside keeps one less level of indentation.

Questions you should ask:

  1. Is bar short function?
  2. Is it pure function, or modifies something?
  3. Is it tightly related to foo, or can be reused elsewhere?
  4. Do you want another level of indentation?
Prostitution answered 6/6, 2018 at 9:50 Comment(0)
P
5

From a correctness/standard standpoint, that is of course OK. Going beyond that, this question is largely opinion-based.

Lambdas are one of the many tools in C++. They add another layer of structure between "copy-paste for reuse" (code smell) and "extract it into a function". I personally enjoy function-local lambdas for exactly the listed purpose: You are reusing a small piece of code that is only meaningful within this function scope.

There are arguments to be made that it should still be its own function (because all your functions are 5 lines or shorter, right? Well, I doubt it...). In particular, if you ever want to reuse that lambda code, it should definitely be its own function. But before that, it is worth considering the benefit of having this code right there next to where it is used, instead of a screen or more of scrolling away. Also, both approaches give a name to the operation, but the lambda requires less overhead to write (e.g. private member function bloat). On the same page, adding a private member function to a header triggers recompiles, adding a lambda within the .cpp does not.

In the end, consider what would make the code most readable. If the difference in code clarity between using a lambda or extracting it into a function is minute (not unlikely), then the convenience of writing the code starts to matter, for which the above considerations come into play.

As for testing, it depends on how fine-grained you want to test. Does a lambda expression like return a && b && (a == b); really need a test? Would you test it if it was just used inline (i.e. not extracted as lambda or function)? At some point it becomes a waste of time, but determining that point is impossible in a SO answer.

Periodical answered 6/6, 2018 at 9:58 Comment(0)
P
4

There is nothing wrong about this, actually this is one big advantage of lambdas: You can define them locally where you need them instead of having to write a functor type. If you like to define/declare your stuff in the most narrowest scope possible, then lambdas are your friend.

You can even go one step further and do something like this:

 auto bar = []() {return true;}();

which defines the lambda and calls it, all in a single line.

Poirier answered 6/6, 2018 at 10:0 Comment(4)
This is interesting for constructors' initialiser lists: C(/*...*/) : m([]( /*... */) { /*... */ } (/*...*/)) { }, especially if the member is not default constructible...Markhor
@Markhor i cannot help myself, but for me the most interesing part is that [](){}(); is valid code, and maybe the most fancy way to write a noop :)Poirier
Hope you don't want to produce true nanosecond delays this way...Markhor
@user463035818: I think []{}() is more esthetically pleasing.Nimbus

© 2022 - 2024 — McMap. All rights reserved.