The Evolutions of Lambdas in C++14, C++17 and C++20
fluentcpp.com
fluentcpp.com
...
> 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 yourself at compile-time, and your code is now more error-prone as you can pass in any argument.
Just add a using-decl (or a namespace alias if the base name isn't meaningful enough for you).
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.
Absl's arguments are that you shouldn't use that structure much, so you should avoid deep nesting (specifically deep nesting, not no nesting). But it actually does something in C++, which isn't true in Java. And that's really the core of Absl's complaints about nesting - don't copy your Java package name (which is irrelevant useless noise) to your C++ code (where it's actual structure, and collisions can result in issues)
If you introduce ambiguity, the code won't compile.
In C++ too bad, you wrote Doodad and thought you were getting ::some::new::package::hierarchy::Doodad however that doesn't actually exist, you were thinking of ::some::long::package::hierarchy::Doodad and your compiler didn't complain because it silently concludes you wanted ::some::Doodad which does exist (maybe it was added years ago and is rarely used, or maybe it was added in the new version you got last week) even though you never actually wanted anything from ::some itself. C++ loves ambiguity, because now it's your fault this happened, you were less than perfect and as such unworthy of the power and grace of C++
Eh, in some cases it's a wash.
std::vector<std::unique_ptr<mynamespace::MyWidget>> 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<mynamespace::MyWidget>` 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 epsilon slower, but that consideration loses to readability any time of day. (Otherwise you could just remove all comments and indentation from your code - that also makes compilation faster!)Yes, it's noise to write:
std::unique_ptr<MyWidget> widget = std::make_unique<MyWidget>();
But I don't think it's so noisy in the case you've provided. If I encountered that code, I'd wonder whether that operator-> is safe, or if you're inadvertently dropping some qualifier (e.g. is it actually a raw pointer?).And as I suggested, it's not much harder to add a using decl:
using UniqueWidget = std::unique_ptr<mynamespace::MyWidget>;
// ...
std::vector<UniqueWidget> objects;
// ...
auto it = std::find_if(objects.begin(), objects.end(), [](UniqueWidget&) { return widget->x == 1; });
(I also recognize that my opinion is very heavily flavored by the Google style guide: https://google.github.io/styleguide/cppguide.html#Type_deduc...)I wouldn't say it's a smaller evolution than previous iterations.
myLambda = [](&&x){ std::cout << x << '\n'; };
what's the point of having to write auto everywhereUnfortunely it is also an hindrance to fix some issues that prevent C++ to ever embrace safety, or better implementation of static analysers.
Declaration syntax is not an impediment to safety or to static analyzers, except insofar as it is trickier to code a parser for them.
int x[100] = {}; // just a regular array
int val = x[[]{return 42;}()]; // compile error!
Once again, greedy parsing is the culprit; here it thinks [[ is the start of an attribute.As a general preference, I'd always want a keyword to introduce a new variable, and most languages seem to agree even if they didn't have to.
I agree with them, in Python the number of times something like this happens:
myVariable = 10
myVariablr = clever_function(myVariable)
(note the typo in variable name)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.
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 assignmentThis really sucks when it happens during a long-running job. Say for example you're looping through an array in chunks and distributing each chunk across GPUs. The last chunk will have fewer elements than all the others, maybe even 1 element. If you don't pad the chunk, it will go through a different (and likely underutilized) code path downstream and crash like this.
You might get a warning that myVariabl_r_ is unused, but that's also not a given. The bar for being 'used' is pretty low. For example, in this
def main():
x = 10
l = []
l.append(x)
return 5
pyflakes believes both x and l are "used", even though neither affects function return value.But if Python had a different syntax like “let var = value” for creating new variables rather than mutating existing ones, we wouldn’t have this specific problem. That’s what this thread is about.
If C++ inferred “auto” for all assignments/initializations, it would have a similar problem where typos in assignments effectively lead to that line of code getting skipped.
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.
Generalised capture
In C++11, lambdas can only capture existing objects in their scope:
int z = 42;
auto myLambda = [z](int x){ std::cout << x << '-' << z + 2 << '\n'; };
...
But with the powerful generalised lambda capture, we can initialise captured values with about anything.
int z = 42;
auto myLambda = [y = z + 2](int x){ std::cout << x << '-' << y << '\n'; };
Am I the only one who doesn't see much of a difference here? std::function<int()> make_func(std::unique_ptr<int> 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).Internally, lambdas are function pointers + the context, and the context may or may not be dynamically allocated depending on how lambdas are used.
// 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<int> numbers) {
my_lambda lam(2);
// remove all the matching elements
erase_if(numbers, lam);
return numbers;
}
// B. Using a lambda
auto g(vector<int> numbers) {
int c = 2;
erase_if(numbers, [c](int x) { return x == c; });
return numbers;
}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
Lambdas are essentially syntactic sugar for an anonymous struct holding references to/copies of captured variables and an operator() method with your code. Not 100% precise, but memory-wise that's how it works (and has to work). No dynamic memory allocation is involved here at all.
If you put a lambda (or any other callable) in a std::function, the std::function will copy that functor object, either into its own small-buffer-optimized in-place storage or (above a certain functor size) into heap-allocated memory.
(Or maybe you're talking about putting a lambda into an std::function, which is a totally different thing and will probably incur heap allocation?)
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.
I have no time to read the standard or keep up with it. Maybe that is what people at Google can afford to do? I'm guessing that is where you work.
I run a small business, and I have to juggle a lot more than just writing code. When I have a problem, I figure out where it's coming from, and fix it. If you run your own business I'm sure you understand that.
Not keeping up with the standard has nothing to do with ignorance or refusal, but with constraints. I also don't think it should be required to know the standard, or the syntax for features you don't need to use everyday of the top of your head. If you are not sure, look it up. That's what the internet is for.
In any case, no need to be arrogant and dismiss people with experience as stupid or ignorant, or blaming them for spreading misinformation, simply because they don't know certain syntax by heart or don't keep up with the standard.
This is an unsubstantiated point and simply untrue. Modern features have worked hard to maintain what is referred to as "zero cost abstractions".
>If you run your own business I'm sure you understand that.
I do own and operate a business, a quant firm engaged in HFT. It's part of the reason why I hire so many C++ developers.
>Not keeping up with the standard has nothing to do with ignorance or refusal, but with constraints.
The ignorance comes from going into an interview where one would presumably be expected to have a basic understand of modern C++ functionality and then proceeding to call that interview BS when in fact it was you who simply had a poor understanding of what was expected.
If you don't want to use lambda expressions or you feel like using C++ like it's still the year 2003, so be it, that is your choice... just don't be surprised and go around calling interviewers full of BS when you continuously refuse to keep your understanding of technology up to date.
The problem in this case is not with the "BS interview" or the interviewer, the problem is with you. If you do not keep your understanding of C++ up to date, for whatever reason, then an interviewer is right to reject you for it in favor of someone who does take the time to keep their skills up-to-date.
1. It can not hold move only callable objects.
2. It heap allocate stored callable object if the object is large enough.
4. Can't be constexpr
Example: https://github.com/facebook/folly/blob/main/folly/docs/Funct...
I am a big friend to push good coding practices into the CI/CD configuration, regardless of the programming language.
Anyone can do whatever they feel like on their own computer, but the overall product has guidelines to follow, specially relevant in projects that live from changing consulting companies all the time.