[cfe-dev] libcxx: std::function should be able to capture blocks

David Blaikie dblaikie at gmail.com
Thu Oct 9 09:47:17 PDT 2014


On Thu, Oct 9, 2014 at 12:11 AM, Jared Grubb <jared.grubb at gmail.com> wrote:

>
> On Oct 3, 2014, at 14:50, David Blaikie <dblaikie at gmail.com> wrote:
> On Fri, Oct 3, 2014 at 12:55 PM, Jared Grubb <jared.grubb at gmail.com>
> wrote:
>
>> I spoke with Marshall briefly about this at CppCon. My suggestion is that
>> std::function should either:
>>  * correctly capture blocks and just work (as this patch does)
>>  * compile with error if you try it (and also maybe add a convenience
>> adaptor like you suggest to allow it to manually work)
>>
>> The current problem is that if you write something like this:
>>    template <typename F> doSomething(F&& functor) { std::function<...>
>> capturing = std::forward<F>(functor); ... }
>> This works great for lambdas. For blocks, it compiles just fine but has
>> runtime issues.
>>
>
> What runtime issues are you referring to? Could you provide a simple
> standalone complete example?
>
>
> Here's an example.
>
> int main(int argc, const char**) {
>     std::function<void()> f;
>
>     std::vector<int> vec = {1, 2, 3};
>     if (argc == 1) {
>         f = ^{ std::cout << vec.size() << " (block)\n"; };
>     } else {
>         f = [=]{ std::cout << vec.size() << " (lambda)\n"; };
>     }
>
>     f();
> }
>
> On my machine, it doesnt crash, but the block part is definitely broken:
>

Fair enough.


> $ ./a.out
> 0 (block)
> $ ./a.out 1
> 3 (lambda)
>
> Other examples, especially multithreaded ones, will crash and behave in
> undefined ways.
>

Hmm - which kind of multithreading do you have in mind? I'm not sure why
that'd be a particular problem.


>
> I assume the issue is that std::function will capture the block's
> (presumably raw) pointer by value but do none of the increment/decrement.
>
>
> Yes.
>

Would it be possible/reasonable to warn/error on copying the raw block
pointer? (passing it to any function at all) std::function isn't the only
entity that could cause this kind of dangling block pointer bug & it might
be nicer to fix it in the language for all such cases, than fix one (albeit
common/easily triggered) case in the library?

I assume I could write the same bug with something like:

T *t; // not sure what 'T' is, but I imagine it's writeable?
if (x) {
  t = ^{ std::cout << vec.size() << " (block)\n"; };
}
t();


>
> This doesn't seem necessarily broken & I'm not sure (for myself) it merits
> library support to protect.
>
>
> This would be the same behavior as any other lifetime issues with pointer
> captures.
>
>   std::function<...> func() {
>     int i;
>     int *j = &i;
>     return [=j] () { ... }; // captured j by value, but it points to
> something that goes out of scope at the end of the function. Bad.
>   }
>
> But similar code isn't a problem at all:
>
>   void func(std::function<...>); // func calls the std::function, maybe
> copies it around, but doesn't copy it to global storage/anywhere that
> outlives the
>   ...
>   int i;
>   int *j = &i;
>   func([=j]() { ... });
>
>
> You're right that it's the same "bad pointer to stack" issue, but the
> problem is at a different level. In the example above, the closures
> themselves are perfectly fine. The problem is that std::function captures
> the closure incorrectly. That's why I think it's a library issue.
>
> Jared
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20141009/e1ce1dc1/attachment.html>


More information about the cfe-dev mailing list