95

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?

Baruch
  • 20,590
  • 28
  • 126
  • 201

6 Answers6

68

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.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 10
    _"since the days of C"_ Way before then my friend – Lightness Races in Orbit Dec 16 '19 at 03:05
  • 4
    @LightnessRaces: My main point was to express that C++'s function syntax has been around since C, so a lot of people know what it is by inspection. – Nicol Bolas Dec 16 '19 at 14:41
  • 2
    Agree with all of this answer except for the first sentence. "It's not normal" is a vague and nebulous statement. Perhaps you meant it in the sense of "it's uncommon and confusing"? – einpoklum Dec 17 '19 at 17:32
  • 1
    @einpoklum: C++ has many things which could be considered "uncommon and confusing" that are still quite "normal" within their domain (see deep template metaprogramming). I think "normal" is perfectly valid in this case; it is not a thing which is within the "norms" of C++ programming experience, both historically and today. – Nicol Bolas Dec 17 '19 at 18:12
  • @NicolBolas: But in that sense, it's circular reasoning: OP is wondering whether we should adopt a rare practice. Rare practices are not "the norm", almost by definition. But nm. – einpoklum Dec 17 '19 at 18:35
  • 1
    @einpoklum: "*OP is wondering whether we should adopt a rare practice*" No, the OP asked why we *shouldn't* adopt the rare practice. The answer to which is that... it is a rare practice. When it comes to a social activity like programming, the best reason to do the normal thing is that other people know what it is. Because it's normal. Norms can change, but only when an alternative is presented that has sufficient and clear advantages to be worth upsetting the norm over. – Nicol Bolas Dec 17 '19 at 21:12
54

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)
AndyG
  • 39,700
  • 8
  • 109
  • 143
  • There's also the implicit "this" pointer to a lambda (even a non-capturing one) which results in an extra parameter when calling the lambda vs calling the function. – 1201ProgramAlarm Dec 15 '19 at 15:44
  • @1201ProgramAlarm: That's a good point; it's can be much more difficult to get a function pointer for a lambda (although non-capturing lambdas can decay into regular function pointers) – AndyG Dec 15 '19 at 15:47
  • 3
    On the flip-side, if it is passed somewhere instead of directly called, having a lambda instead of a function promotes inlining. Naturally, one could pack the function-pointer in a `std::integral_constant` for that... – Deduplicator Dec 16 '19 at 02:09
  • 1
    @Deduplicator: "*having a lambda instead of a function promotes inlining*" Just to be clear, this only applies if the "somewhere" is a template that takes the function it calls as an arbitrary callable type, rather than a function pointer. – Nicol Bolas Dec 16 '19 at 04:00
  • @KonradRudolph: The only type that is allowed to take up no space is an empty base class during optimization. I did test the size of a lambda. [here](https://wandbox.org/permlink/vyfMBvAE5V4rKbVA) – AndyG Dec 16 '19 at 14:36
  • @AndyG: The result of `sizeof` is not really the point. The point is that, if you never take that lambda's address or do something that actually *requires* the compiler to allocate storage for it, then the compiler is free under the "as if" rule to make that variable disappear. – Nicol Bolas Dec 16 '19 at 14:37
  • 2
    @AndyG You’re forgetting the as-if rule: Programs that don’t request the size of the lambda (because … why?!), nor create a pointer to it, don’t need to (and generally don’t) allocate space for it. – Konrad Rudolph Dec 16 '19 at 14:38
  • @KonradRudolph: Re: stateful lambdas. If you are going to make them global, make them actual classes. It's much easier to deal with. You can default construct them, among other things. – AndyG Dec 16 '19 at 14:38
  • @AndyG But I don’t *want* to default-construct them. I just want to use them as functions (actually, I don’t, because I agree with Nicol’s answer, and the first comment on your answer — but the reasons you’ve given are almost all bad/wrong reasons). – Konrad Rudolph Dec 16 '19 at 14:39
  • @NicolBolas: Ultimately you won't ever have to worry about whether that happens or not if you just used functions? – AndyG Dec 16 '19 at 14:39
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/204349/discussion-between-konrad-rudolph-and-andyg). – Konrad Rudolph Dec 16 '19 at 14:44
10

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…

Michael Kenzel
  • 15,508
  • 2
  • 30
  • 39
9

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.

Baruch
  • 20,590
  • 28
  • 126
  • 201
  • 3
    You [could](https://stackoverflow.com/a/32697323/1593077) make them `constexpr` lambdas... the real point is: Just use a function. – einpoklum Dec 17 '19 at 17:29
7

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.
Jarod42
  • 203,559
  • 14
  • 181
  • 302
7

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.

Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
  • 1
    This seems to be an argument from terminology, ie. confusing naming and meaning. It can easily be refuted by defining that a named lambda is no longer anonymous (duh). The issue here isn’t how the lambda is used, it’s that “anonymous function” is a misnomer, and no longer accurate when a lambda is bound to a name. – Konrad Rudolph Dec 16 '19 at 14:34
  • @KonradRudolph: It's not just terminology, that's why they were created in the first place. At least historically, lambdas are anonymous functions, and that's why it's confusing to name them, especially when functions would be expected instead. – Eric Duminil Dec 16 '19 at 18:18
  • 2
    Nah. Lambdas are *routinely* named (just usually locally), thereʼs nothing confusing about that. – Konrad Rudolph Dec 17 '19 at 00:08
  • 1
    Disagree with *anonymous functions*, it is more a functor, but anyway, I don't see the problem to have (named) instance instead of forcing to use them only as rvalue-reference. (as your point should also apply at local scope). – Jarod42 Dec 17 '19 at 19:08