What this means is that whenever you pass off a lambda to a function, and that lambda captures a variable, you need to be aware of whether or not the function keeps a reference to the lambda someplace, so that you'll know if it's safe to capture by reference or not. If you capture by reference but the function keeps a reference to the lambda, and it gets called after the captured variable has gone out of scope, you'll get into some nasty trouble.
And this is why I don't like the C++ spec as it stands. It requires a kind of global knowledge to work with correctly in local contexts, in such a way that the compiler can't really help you either (it may have been possible to annotate types to indicate closure lifetime, but it would be painful without more powerful type inference than C++ has).
You could get around this problem with spaghetti stacks or something, but you'd need to find a way to free the activation records that are floating around after their enclosing scopes have expired -- enter garbage collection which you do NOT want to require for C++.
That's the problem with Lisp, it's almost all-or-nothing. If you want to correctly include some of the benefits of the Lisp execution model -- like lambdas -- you need to accept the whole thing, lock, stock, and barrel. Including heap-allocated local vars and the garbage collector. (And yes -- Python, Ruby, Haskell, and Standard ML have a "Lisp execution model" in this sense.)
So we get compromises and hulking abominations like C++0x lambdas or -- worse yet -- their predecessor, Boost Lambdas.
tl;dr: Upward funargs are hard, let's go shopping.
int f() {
int x = 42;
return dupclosure(^(){return x+10}};
}
int g()
{
int (^fn)();
fn = f();
fn();
freeclosure(fn);
}
It's not quite as pretty, but it's workable, and in line with a C-family language's semantics. void* closureheap(void (^)())
size_t closuresize(void(^)())
Or, well, anything else that allows you to separate the step of allocating memory from it's initialization. (There are probably more representation-independent APIs that would be better, but this was just off the top of my head)And, yes, mutable captured variables will not be shared across multiple duplications. Somewhat unconventional, but given the mental model of copying closures, I don't think it's a surprising behavior.
Objective-C blocks automatically copy those variables to the heap. (Which is not to say they're not a bunch of compromises.)
Ultimately Garbage Collection would help there, but I am not sure we are going to see that in C++ anytime soon.
When I designed and implemented the same feature, anonymous methods in Delphi, I used reference counting to keep alive a heap-allocated activation record containing all captured variables. This works well for most scenarios; it can get into knots in more obscure situations where you have recursive lambdas that call themselves via a captured variable, but those are usually pretty rare.
You're right that GC is a help. The biggest thing GC gives you is freedom from having to worry about who controls the lifetime of parameters and function return values, in most cases. In the presence of GC, you can get more clever about your algorithms and data structures; you can cache and memoize, without paranoid concern for things disappearing behind your back. Consider a querying API that takes in closures for sorting and selection functions; I can see it building up temporary results and caching them, or streaming results in a multi-threaded fashion, but it can only do that if it can reliably hang on to closures after the select/sort/etc. function has returned.
Ultimately the C++ problems have to be resolved by conventions and idioms, just like we've all learned to be careful when passing a pointer to a local variable.
I'm not really sure how GC works but using normal memory allocation when you can and reference counted pointers when somebody else is responsible for deallocating objects seems to be adequate and is still high performance...
I just wish the actual passive-aggressive fight between the FSF and Apple would resolve and blocks could finally make it into upstream GCC C compiler.
I understand why the FSF wants copyright assignment, but it makes the process a lot longer and more complicated.
XCode 3.2 defaults to GCC 4.2 with LLVM optionally available and IIRC XCode 4 too (can't check as I downgraded for various reasons).
The blocks patchset against pure GCC exists, and the problem mostly lies in upstream GCC refusing patches whose copyright has not been assigned/transferred to the FSF (see https://lwn.net/Articles/405417/). The rationale is that a critical component such as GCC should not be at the mercy of multiple (possibly hundreds) conflicting parties and easing a possible relicense process to ensure its protection.
While I understand the rationale behind this, my opinion is that it feels bureaucratic to the point of hampering notable innovative contributions while favoring local forks which will inevitably end up dying, as maintaining a fork (whatever the patchset size) against the march of a behemoth like GCC is essentially hopeless.
Xcode 4 defaults to llvm-gcc, not gcc-4.2.