logoalt Hacker News

ueckeryesterday at 1:12 PM4 repliesview on HN

Lambdas are just anonymous nested functions. But I like named nested functions more because they are more readable and would prefer them in most cases. Ideally you have both as most languages have.

I always wondered why C++ only added lambdas, but observing WG21 for a while, I assume this is just a random walk in language design. (not that it is different in WG14)


Replies

wasmpersonyesterday at 2:42 PM

> Lambdas are just anonymous nested functions.

The important feature of lambdas is that they are expressions, not that they lack a name. The advantage of function expressions is you can write the body of the function exactly at the place where it is used. With GCC nested functions you either have to write the body of the function before its first use or else write the declaration of the function twice.

This matters for long chains of continuation passing:

  foo(arg1, arg2, [](){
          // do some work
          bar(arg3, arg4, [](){
                  // do some more work
                  baz(arg5, arg6, [](){
  
                  });
          });
  });
Compare to the following, where the control flow is all out of order:

  void cb(void){
          // Do some work
          void cb2(void){
                  // do some more work
                  void cb3(void){

                  }
                  baz(arg5, arg6, cb3);
          }
          bar(arg3, arg4, cb2);
  }
  foo(arg1, arg2, cb);
show 1 reply
eruyesterday at 2:10 PM

I can write numbers like three by just writing 3 in my code. When I want a named number I use a syntax like x = 3. Why should functions be any different? A language doesn't need different ways to name things for each type of thing. Integers, strings, functions etc: they can all use the same mechanism for naming.

show 2 replies
astrobe_yesterday at 6:10 PM

No exactly. "Lambdas" are usually function closures [1]. Which do not exist in C and were quite "late" in C++, because decent support of closures require automatic memory management (GC).

C++ lambda/closures are a bit clunky because you have to specify if the captures are by reference or by value, and you're better of having a good idea of what you're doing.

[1] https://en.wikipedia.org/wiki/Closure_(computer_programming)

show 1 reply
sltkryesterday at 6:32 PM

Lambda expressions in C++ are simply syntactic sugar for defining function objects (aka functors): structs that overload operator() so you can call them as functions. Once you realize this, their features and limitations become immediately clear.

For example, here is a typical use of a lambda expression to filter a vector of values:

    #include <iostream>
    #include <vector>

    int main() {
        std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6, 5};

        int threshold = 5;
        std::erase_if(v, [&](int i) { return i < threshold; });

        // prints 5 9 6 5
        for (int i : v) std::cout << i << '\n';
    }

The lambda expression is essentially shorthand for:

        ...
        int threshold = 5;
        struct lambda_t {
            int &threshold;
            bool operator()(int i) {
                return i < threshold;
            }
        };
        std::erase_if(v, lambda_t{threshold});
        ...
You could always do this in C++. The added value of the lambda expression syntax is that the compiler generates the boilerplate, and generates a unique name for lambda_t.

The important takeaway is that every lambda expression corresponds with a unique type that is _not_ a function type, but a class type. Consequently, lambda expressions can only be passed to template functions like std::erase_if, which are parameterized with the callback type.

You cannot pass a lambda expression to a function that expects a function pointer (e.g. bool(*)(int) in this example), and that's where they differ from GCC-style nested functions, which actually behave like functions. It also explains why lambda expressions don't need a trampoline.

As an aside, you _can_ pass lambdas to non-generic functions using a type-erasing wrapper like std::function, but std::function is itself a class type too, so that still doesn't allow you to convert it to a plain function pointer.

Finally, you can of course assign a name to a lambda expression value, using this common pattern:

    auto greet = [](const char *name) { std::cout << "Hello " << name << "!\n"; }
    greet("Alice");
    greet("Bob");
(Note that `auto` is necessary here because there is no way to explicitly refer to the compiler-generated name for the lambda type.)

This is the closest you can get to a local function definition in C++. Admittedly the syntax is a little odd. You might wonder why there wasn't some additional syntactic sugar to make the definition look more normal. I suspect that wasn't a random decision, but rather intentionally avoiding conflicts with existing language extensions like GCC's local function syntax.

show 3 replies