It's a trade off more than a necessity. For example, Rust doesn't have explicit capture lists, and if you want explicit control, you make new bindings and capture those. You almost never need to do this in Rust, so it's optimized for that case; I haven't written many closures in C++, so I can't say as much about the frequency there.
To make this more concrete:
let s = String::from("s");
let closure = || {
println!("s is: {}", s);
};
closure();
If you wanted to capture s in a different way:
let s = String::from("s");
let s1 = &s;
let closure = || {
println!("s1 is: {}", s1);
};
closure();
Rust doesn't have explicit capture lists, but it does have the `move` modifier on closures which is like C++'s `[=]`.
Strictly speaking, Rust probably could have gotten away with having neither the `move` modifier nor capture clauses at all, but it would have had wide-ranging implications on the ergonomics and capabilty of closures.
What if you want to move some things, but copy others?
e.g.
auto shared = std::make_shared<MySharedType>(...);
auto unique = std::make_unique<MyOwnedType>(...);
function_that_accepts_lambda([shared, u = std::move(unique)] {
shared->foo(...); u->bar(...);
});
// Outer scope can still use shared.
shared->foo(....);
If the type implements Copy they'll be implicitly copied when moved into the Rust closure(I think). Or you can declare a scope var and clone() manually.
The syntax is not the prettiest, but it is legible once you understand what [](){} means.
In C#, there is no such thing, but there is a part of me that wishes we had such a thing. I like the ability explicitly state what variables are being captured.
No. What I have done on the other hand was add unused lexical variables to an anonymous function so the runtime wouldn't optimise them out of the closure and I could still see them in the debugger.
I'm not 100% sure, but C#'s compiler should automatically capture what you need (and leave out the rest).
I think the primary need for manual declaration is because in C++ you need to differentiate between pass by copy semantics and pass by reference semantics.
> I think the primary need for manual declaration is because in C++ you need to differentiate between pass by copy semantics and pass by reference semantics.
That's not actually a need, C++ includes [=] and [&] (capture everything by value or by reference). You can get a mix by creating references outside the body then capturing the environment by value (capturing the references by value and thus getting references).
On the one hand it has a bit more syntactic overhead (you have to take and declare a bunch of references before creating the closure), on the other hand there's less irregularity to the language, and bindings mean the same thing in and out of the closure.
FWIW that's what Rust does[0], though it may help that Rust's blocks are "statement expressions", some constructs would probably be unwieldy without that.
[0] the default corresponds to C++'s [&] (capture by ref), and a "move closure" switches to [=] instead
Yep, in Microsoft’s C# compiler, only the closed over local variables of a function are captured (which, in C#’s case, means generating a class with fields corresponding to each closed over local, and then replacing those locals with references to their respective fields of an instance of that class).
The only thing I find somewhat frustrating about the syntax is that the notation messes with my existing expectations. Up until now, in C-like languages a [] was just for collections and indexing into them, in the code I used at least.
I mean I'm not really complaining; I don't see better syntax to fit short anonymous functions into the existing syntax, without defeating the whole purpose of it either.
I suspect it's just a matter of getting used to this extra meaning for angular brackets.
Well, you need something to indicate the beginning of a lambda. So you can think of "[]" as serving that role, instead of "lambda" in Python or "\" in Haskell.
The declaration for lambda variables is almost identical to function pointers, just with a ^ instead of a *, so there's nothing to learn (or unlearn, like C++ forces you to). The ^ looks like a lambda, and historically the lambda of lambda calculus actually was a caret accent over the variable. The argument list can be elided.
Looking here (https://developer.apple.com/library/ios/documentation/Cocoa/...) for the details, I think this is going to largely be a matter of opinion. I prefer the C++ syntax, especially when it comes to capture by reference, for which the Apple syntax seems to require the __block storage type modifier.
Caret as the embryonic form of lambda is apparently a myth propagated by Barendregt, and lambda is just a random Greek letter to go with alpha, beta, and eta.
Agreed. From the template library on down, it seems like the C++ community is hellbent on making the syntax for what should be clean, common operations seem like arcane Sanskrit. I dont know what their problem is.
Often, the problem is that the clean simple syntax you might want to use already means something else in C++, and the bias against breaking existing code is very strong.
Refusing to break backwards compatibility is their problem. I respect them for that; if you do want to break it make another language that plays nicely with C++ instead.