Any reason not to use global lambdas?

C++Lambda

C++ Problem Overview


We had a function that used a non-capturing lambda internal to itself, e.g.:

void foo() {
  auto bar = [](int a, int b){ return a + b; }

  // code using bar(x,y) a bunch of times
}

Now the functionality implemented by the lambda became needed elsewhere, so I am going to lift the lambda out of foo() into the global/namespace scope. I can either leave it as a lambda, making it a copy-paste option, or change it to a proper function:

auto bar = [](int a, int b){ return a + b; } // option 1
int bar(int a, int b){ return a + b; } // option 2

void foo() {
  // code using bar(x,y) a bunch of times
}

Changing it to a proper function is trivial, but it made me wonder if there is some reason not to leave it as a lambda? Is there any reason not to just use lambdas everywhere instead of "regular" global functions?

C++ Solutions


Solution 1 - C++

There's one very important reason not to use global lambdas: because it's not normal.

C++'s regular function syntax has been around since the days of C. Programmers have known for decades what said syntax means and how they work (though admittedly that whole function-to-pointer decay thing sometimes bites even seasoned programmers). If a C++ programmer of any skill level beyond "utter newbie" sees a function definition, they know what they're getting.

A global lambda is a different beast altogether. It has different behavior from a regular function. Lambdas are objects, while functions are not. They have a type, but that type is distinct from the type of their function. And so forth.

So now, you've raised the bar in communicating with other programmers. A C++ programmer needs to understand lambdas if they're going to understand what this function is doing. And yes, this is 2019, so a decent C++ programmer should have an idea what a lambda looks like. But it is still a higher bar.

And even if they understand it, the question on that programmer's mind will be... why did the writer of this code write it that way? And if you don't have a good answer for that question (for example, because you explicitly want to forbid overloading and ADL, as in Ranges customization points), then you should use the common mechanism.

Prefer expected solutions to novel ones where appropriate. Use the least complicated method of getting your point across.

Solution 2 - C++

I can think of a few reasons you'd want to avoid global lambdas as drop-in replacements for regular functions:

  • regular functions can be overloaded; lambdas cannot (there are techniques to simulate this, however)
  • Despite the fact that they are function-like, even a non-capturing lambda like this will occupy memory (generally 1 byte for non-capturing).
  • as pointed out in the comments, modern compilers will optimize this storage away under the as-if rule

"Why shouldn't I use lambdas to replace stateful functors (classes)?"
  • classes simply have fewer restrictions than lambdas and should therefore be the first thing you reach for
  • (public/private data, overloading, helper methods, etc.)
  • if the lambda has state, then it is all the more difficult to reason about when it becomes global.
  • We should prefer to create an instance of a class at the narrowest possible scope
  • it's already difficult to convert a non-capturing lambda into a function pointer, and it is impossible for a lambda that specifies anything in its capture.
  • classes give us a straightforward way to create function pointers, and they're also what many programmers are more comfortable with
  • Lambdas with any capture cannot be default-constructed (in C++20. Previously there was no default constructor in any case)

Solution 3 - C++

> Is there any reason not to just use lambdas everywhere instead of "regular" global functions?

A problem of a certain level of complexity requires a solution of at least the same complexity. But if there is a less complex solution for the same problem, then there is really no justification for using the more complex one. Why introduce complexity you don't need?

Between a lambda and a function, a function is simply the less complex kind of entity of the two. You don't have to justify not using a lambda. You have to justify using one. A lambda expression introduces a closure type, which is an unnamed class type with all the usual special member functions, a function call operator, and, in this case, an implicit conversion operator to function pointer, and creates an object of that type. Copy-initializing a global variable from a lambda expression simply does a lot more than just defining a function. It defines a class type with six implicitly-declared functions, defines two more operator functions, and creates an object. The compiler has to do a lot more. If you don't need any of the features of a lambda, then don't use a lambda…

Solution 4 - C++

After asking, I thought of a reason to not do this: Since these are variables, they are prone to Static Initialization Order Fiasco (https://isocpp.org/wiki/faq/ctors#static-init-order), which could cause bugs down the line.

Solution 5 - C++

Lambdas are anonymous functions.

If you are using a named lambda, it means you are basically using a named anonymous function. To avoid this oxymoron, you might as well use a function.

Solution 6 - C++

> if there is some reason not to leave it as a lambda? Is there any reason not to just use lambdas everywhere instead of "regular" global functions?

We used to use functions instead of global functor, so it breaks the coherency and the Principle of least astonishment.

The main differences are:

  • functions can be overloaded, whereas functors cannot.
  • functions can be found with ADL, not functors.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBaruchView Question on Stackoverflow
Solution 1 - C++Nicol BolasView Answer on Stackoverflow
Solution 2 - C++AndyGView Answer on Stackoverflow
Solution 3 - C++Michael KenzelView Answer on Stackoverflow
Solution 4 - C++BaruchView Answer on Stackoverflow
Solution 5 - C++Eric DuminilView Answer on Stackoverflow
Solution 6 - C++Jarod42View Answer on Stackoverflow