5 ms·
Taking a concept such as a lambda function and making it look this ugly...this is why I hate C++. I wish I wasn't forced to program it every day.
by typon 10y ago
Taking a concept such as a lambda function and making it look this ugly...this is why I hate C++. I wish I wasn't forced to program it every day.
- hendzen 10y agoHow is it ugly? The capture list is a necessary complexity in a language with manual memory management.
- steveklabnik 10y agoIt'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(); No capture list needed, same control.
- kibwen 10y agoRust 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.
- hendzen 10y agoHow would you do the equivalent of this in Rust? auto on_heap = std::make_unique<MyType>(...); function_that_accepts_lambda([obj = std::move(on_heap)]() { obj->bar(...); })
- steveklabnik 10y agolet on_heap = Box::new(...); function_that_accepts_lambda(move || { on_heap.bar(); }); This is sort of what kibwen was mentioning: move is a single annotation that overrides everything to capture by value rather than have it inferred.
- hendzen 10y agoWhat 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(....);
- vvanders 10y agoIf 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.
- steveklabnik 10y agoI am 99% sure this is identical: let on_heap = Box::new(...); let shared = Arc::new(...); let s = shared.clone(); function_that_accepts_lambda(move || { on_heap.bar(); s.foo(); }); We have to make the extra s binding. Also, my sibling is correct that Copy types will just be copied, not moved.
- partisan 10y agoThe 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.
- rtpg 10y agoI'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.
- noblethrasher 10y agoYep, 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).
- masklinn 10y ago> 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
- masklinn 10y ago> I like the ability explicitly state what variables are being captured. Why? You state what variables are being captured by just using them in the lambda body.
- kibwen 10y agoVery often the capture list is empty, it could have been elided (as the parameter list can be) if the syntax could have been made unambiguous.
- yongjik 10y agoWell, 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.
- rubber_duck 10y agoYou can use C# fat arrow syntax, it even allows removing the braces for single expression lambdas which is the most common from anyway.
- vanderZwan 10y ago> removing the braces If I have learned anything over the years, it's that removing the braces anywhere is the introduction of a bug during a rewrite waiting to happen.
- rubber_duck 10y agoIt really works without any issues in C# from my experience. Statemends like : list.Where(e => e.Property == ExpectedValue).Select(e => e.Property) is much cleaner than something like : list.Where([](auto e) { return e.Property == ExpectedValue; }).Select([](auto e) { return e.Property; }); Braces and capture declaration adds 0 value here and it's ~80% of the use cases I see for lambdas.
- vanderZwan 10y agoThe 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.
- anon946 10y agoWhat would you propose as the syntax?
- user982 10y agoApple extended C with block closures years ago. int b = 0; ^(int a) { return a*b; } 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.
- anon946 10y agoLooking here (https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html 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.
- CJefferson 10y agoAnd this is why C++ couldn't use this notation, as they didn't want to break compability with this extension of Apple's.
- fanf2 10y agoCaret 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. http://researchblogs.cs.bham.ac.uk/thelablunch/2016/05/why-is-lambda-calculus-named-after-that-specific-greek-letter-do-not-believe-the-rumours/ http://researchblogs.cs.bham.ac.uk/thelablunch/2016/05/why-i...
- optforfon 10y agoI honestly would have much preferred they had a keyword so that it kinda matched the rest of the language. It feels a bit tacked on and hard to parse. Something like: lambda(arguments):capture list {...} Just seems way more clear. Looks more like a function or a class (and a lambda is sort of an in-between kind of thing anyway)
- aswanson 10y agoAgreed. 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.
- vanderZwan 10y agoRefusing 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.
- bboreham 10y agoOften, 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.
- bboreham 10y agoFor historical interest, compare an early proposal: http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2006/n1958.pdf http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2006/n195...