Live data from Hacker News

The Evolutions of Lambdas in C++14, C++17 and C++20

fluentcpp.com

51–60 of 75 posts

Re: The Evolutions of Lambdas in C++14, C++17 and C++20

#51

Generalised capture In C++11, lambdas can only capture existing objects in their scope: int z = 42; auto myLambda = [z](int x){ std::cout Am I the only one who doesn't see much of a difference here?

Generalized capture is primarily useful for move semantics:

   std::function make_func(std::unique_ptr ptr) {
      return [p = std::move(ptr)]() { return *p; }
   }
Without generalized capture syntax, there's no good way to transfer ownership into the closure (a regular `[ptr]` capture would attempt to make a copy, and a by-ref `[&ptr]` capture would lead to a use-after-free).

Re: The Evolutions of Lambdas in C++14, C++17 and C++20

#53

Earlier quoted context omitted.

Problems with std::function:- 1. It can not hold move only callable objects. 2. It heap allocate stored callable object if the object is large enough.

Can you point to better alternate ways or idioms?

(joke) : Common Lisp

Re: The Evolutions of Lambdas in C++14, C++17 and C++20

#54

Earlier quoted context omitted.

That are used to hold captured variables and values. Internally, lambdas are function pointers + the context, and the context may or may not be dynamically allocated depending on how lambdas are used.

Internally, lambdas are structs with the "function call" operator overload. Context is done via members of the struct: capture by-value, and the struct copies them and stores its own, capture by reference, and the struct member is a reference. There should be zero heap allocation in any case.

If I'm reading this right, then "lambda" in C++ isn't "lambda" in Scheme/CommonLisp, where captured variables need be heap allocated rather than stack allocated to construct closures?

Re: The Evolutions of Lambdas in C++14, C++17 and C++20

#55
post #27

Earlier quoted context omitted.

Yes, deeply nested namespaces are not nice. I think boost really dropped the ball on this. boost::asio::ip::tcp should have been boost::tcp. Are they trying to be as bad as java?

As the article explains it's not "as bad as Java" it's worse, what Java does actually makes sense in Java, maybe you have to type slightly more characters but Java is already a verbose language best suited to heavy tool-assist. However it doesn't make sense in C++ because the benefits Java gets don't apply and the price is heavier. There are a bunch of things like this in C++ where superficially C++ feature X is like…

> There are a bunch of things like this in C++ where superficially C++ feature X is like feature Y in another language, and so C++ programmers wrongly assume the problems with feature X must also plague feature Y. I'm sure the reverse happens too.

Yes! I've noticed this also. Several recent C++ changes are copying idioms from other languages, and sometimes the standards committee seems to have missed the point or misunderstood how the features are used in the language they're copying.

The results are superficially similar, but not quite what I'd expect after knowing the feature from the source language.

Re: The Evolutions of Lambdas in C++14, C++17 and C++20

#56
post #22

Earlier quoted context omitted.

>> in Python the number of times something like this happens Anec-data but I’ve been writing python for 15+ years, I’ve contributed to various popular open source projects. I’ve never seen this in a code review. I’ve certainly never seen this kind of mistake released.

I've seen a variation of this bug several times in just the last year: myVariable = 1 if not something_unusual(): myVariablr = 2 return myVariablr This code will work just fine until something_unusual() returns True and then it crashes with UnboundLocalError: local variable 'myVariablr' referenced before assignment This really sucks when it happens during a long-running job. Say for example you're looping through an…

VSCode (pylance) & PyCharm both immediately identify myVariable as unused - this is inline in the editor, before any compilation or testing.

Assuming somehow this code made it into a pull request (although as above, there would be no reason for that) - then flake8 flags myVariable as unused, your CI pipeline wouldn't get as far as notifying a peer that the PR is ready for review.

Re: The Evolutions of Lambdas in C++14, C++17 and C++20

#57

Earlier quoted context omitted.

Internally, lambdas are structs with the "function call" operator overload. Context is done via members of the struct: capture by-value, and the struct copies them and stores its own, capture by reference, and the struct member is a reference. There should be zero heap allocation in any case.

If I'm reading this right, then "lambda" in C++ isn't "lambda" in Scheme/CommonLisp, where captured variables need be heap allocated rather than stack allocated to construct closures?

You're right. Lambdas in C++ are syntactic sugar over something the core language has allowed you to do since before C++98. Since you control which variables you capture into the closure, and how, when writing the lambda, all of the information regarding how to make it (e.g. it's size, data members, etc) is static, and so it does not require the use of the heap at all. A concrete example. The following C++ examples do the same thing, and neither touch the heap:

    // A. Using a struct. The old manual way.
    struct my_lambda {
        my_lambda(int c) : c_(c) {}
        int operator() (int x) const {
            return x == c_;
        }
        int c_;
    };
    
    auto f(vector numbers) {
        my_lambda lam(2);
        // remove all the matching elements
        erase_if(numbers, lam);
        return numbers;
    }

    // B. Using a lambda
    auto g(vector numbers) {
        int c = 2;
        erase_if(numbers, [c](int x) { return x == c; });
        return numbers;
    }

Re: The Evolutions of Lambdas in C++14, C++17 and C++20

#58

Earlier quoted context omitted.

Internally, lambdas are structs with the "function call" operator overload. Context is done via members of the struct: capture by-value, and the struct copies them and stores its own, capture by reference, and the struct member is a reference. There should be zero heap allocation in any case.

If I'm reading this right, then "lambda" in C++ isn't "lambda" in Scheme/CommonLisp, where captured variables need be heap allocated rather than stack allocated to construct closures?

It is complicated. In C++ closures can close over local variables either by value or by reference (the choice can be made for each variable closed over).

When closing over a variable by reference, if the lambda need to survive the local scope heap allocating the closure itself won't help. Instead you need to explicitly heap allocate the closed over variable itself (and close over the, usually smart, pointer).

When closing over by vale, there is no such issue, closed over variables are copied over along the lambda and it can be safely, for example, be returned from a function.

Copying might be expensive if the lambda is closing over an expensive to copy object, but move semantics are always an option.

Lambdas are value types, they are usually copied around. so when closing over ither va

Re: The Evolutions of Lambdas in C++14, C++17 and C++20

#59
post #25

i was in a leetcode BS interview a while ago with a googler who thought i could not write code in c++ because i could not remember lambda syntax. i only use maybe 30% of c++ features in my software and have been doing this for over 20 years. it’s only recently i started using lambdas more, but still stay away from them because of the potential hidden allocations.

I interview a lot of C++ developers who have 20 or more years of experience I feel really bad when the vast majority of them fail to keep up with modern standards or take the time to understand their tools resulting in all kinds of misconceptions based on outdated information.

Lambda expressions do not involve any kind of hidden allocations, their definition is precisely formalized and can be reviewed in S 7.5.5 of the standard. Even if you don't care to read the standard, there is no shortage of resources online that explain what a lambda expression is, and none of them involve anything to do with hidden allocations:

https://en.cppreference.com/w/cpp/language/lambda

I'm sorry to pick on you specifically, but it's a major problem that is entirely unnecessary. It's almost heartbreaking that the people who should have the most experience in a subject based on decades of knowledge are often the ones who carry the biggest misconceptions and spread the most misinformation.

Re: The Evolutions of Lambdas in C++14, C++17 and C++20

#60
post #23

> Even if you don’t need to handle several types, this can be useful to avoid repetition and make the code more compact and readable. ... > namespace1::namespace2::namespace3::ACertainTypeOfWidget Deeply nested namespaces are problematic in themselves due to namespace resolution (e.g. see https://abseil.io/tips/130 ), but templates should never be an answer to "my type is too long to type out". You're hurting yoursel…

> templates should never be an answer to "my type is too long to type out" Eh, in some cases it's a wash. std::vector > objects; // ... auto it = std::find_if(objects.begin(), objects.end(), [](auto& widget) { return widget->x == 1; }); Sure, you could repeat the type `std::unique_ptr ` in the lambda, but that's just noise. You don't spell out the types like that either in, say, C#. Yes, compilation time is an epsilo…

Is it really slower ? The compiler has to compute the type of the right hand side in any case. It just has one less computation to do now (type checking the conversion to the left hand type)
Post reply on HN