3 ms·
Well, there are lot's of well known compiler techniques to handle closures in that context. It's a little bit out of scope of the article, but it's not very com
by Drup 8y ago
Well, there are lot's of well known compiler techniques to handle closures in that context. It's a little bit out of scope of the article, but it's not very complicated either.
- chrisseaton 8y agoI know that but you said the constraint needed was lexical scoping - that constraint is insufficient and you need additional constraints on the language design.
- klmr 8y agoIf you’re happy for the compiler to copy variables out of the closed-over scope when returning the closure, this can still be handled with a single stack. That’s what C++ lambdas do: “Closures” in C++ are locally-allocated structures that hold used variables as local (stack-allocated) member variables. Creating a closure copies closed-over variables (or pointers/references to them). Returning a closure from a function returns a logical copy (which can be optimised away) of the structure. (Don’t get me wrong, this obviously still implies additional constraints, but it gets fairly close to universal closures.)
- chrisseaton 8y agoI don't really understand that point of view - you can use a single stack as long as you actually use the heap in addition to a single stack?
- kccqzy 8y agoC++ closures do not, by themselves, use the heap. Every closure in C++ gets translated by the compiler to a unique type that contains either copies of or references to the objects being closed over. If you choose to use copies, then whether or not anything is allocated on the heap depends on the copy constructor; if you use references then there is no copying, but it's up to you to ensure lifetime.
- jdmichal 8y agoThis is roughly how Java works with anonymous types closing over variables also. That's why the variables must be declared `final`. It just copies the local values over into the anonymous type and calls it a day. Of course, since the only thing allocated on the stack are primitives and pointers, and everything on the heap is subject to garbage collection, this is a pretty straight-forward operation. I don't know if lambdas work the same way. I know in some ways they work like anonymous types, and not in others.
- _old_dude_ 8y agoyes, it works the same way with lambdas, the lambda proxy (the class that implements the functional interface) contains the copy of the local values. Here is the code that generate the constructor of a lambda proxy http://hg.openjdk.java.net/jdk/jdk/file/3cabb47758c9/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java#l356 http://hg.openjdk.java.net/jdk/jdk/file/3cabb47758c9/src/jav...
- klmr 8y agoC++ lambdas do not use the heap. As I wrote, they are stack allocated.
- chrisseaton 8y agoYou're talking about undefined behaviour - this is a badly broken feature of C++ because it does not move the local variable it references onto the heap or some other alternate data structure. Look up the upwards funarg problem. > The upwards funarg problem arises when the calling function refers to the called/exited function's state after that function has returned. Therefore, the stack frame containing the called function's state variables must not be deallocated when the function returns, violating the stack-based function call paradigm. C++ violates the constraint we're talking about.
- klmr 8y agoNo, I’m not talking about UB. Although you’re right that if you use references rather than copies and return a lambda then, yes, you’ll run into UB. The solution to this is to use copies, as mentioned. > Look up the upwards funarg problem. I’m well aware of upward funargs and what I’ve described specifically implements upward funargs in C++. See https://godbolt.org/z/KZiBNB https://godbolt.org/z/KZiBNB. Note that this code does not perform any heap allocation, and the closed-over variable `i` is saved from the local scope and made available to the caller via the lambda.
- chrisseaton 8y ago> Although you’re right that if you use references rather than copies and return a lambda then, yes, you’ll run into UB. The solution to this is to use copies, as mentioned. That was the context I thought we were having the conversation in - capturing local variables. I'd describe what you mean as capturing local values. If you're copying the value from a variable then you're not capturing the variable. I can see why you're arguing it your way now.
- kccqzy 8y ago> it does not move the local variable it references onto the heap C++ doesn't do such thing automatically, but there's nothing preventing you to do it yourself. If you intend to use a local variable whose lifetime would not normally outlast the closure, feel free to use std::move and a move constructor. In general C++ gives you the tools to manage memory manually or semi-automatically, but not totally automatically. And it also doesn't, unlike Rust, force you to use memory correctly.