c++ closure creation

Viewed 945
#include <iostream>
#include <algorithm>
#include <vector>

int main()
{
  // Block 1
  {
    auto inc = []() { int i = 0; return [&]() { return i++; }; }();
    std::vector<int> v(10, 10);
    std::generate(v.begin(), v.end(), inc);
    for (auto i : v) std::cout << i << std::endl;
  }

  // Block 2
  {
    auto inc = []() { int i = 0; return [&]() { return i++; }; };
    std::vector<int> v(10, 10);
    std::generate(v.begin(), v.end(), inc());
    for (auto i : v) std::cout << i << std::endl;
  }
}

I am not sure why these two blocks produce different results.

Block 1: 32767 ... 32776
Block 2: 0 ... 10

And for std::generate the generator(inc) is passed by value, so I believe it should be fine, right?

I am running OS X.

Thanks,


Note that the results from above code are undefined, see below.

1 Answers

I am not sure why these two blocks produce different results.

Both are undefined behavior, so the question is moot. In either case, we have a lambda like:

auto f = []() { int i = 0; return [&]() { return i++; }; };

And f() returns a lambda that has a dangling reference: i is destroyed at the end of the call to f. It's immaterial when that dangling reference happens - whether it happens long before the generate() call or into the generate() call.

If you want to make a generating counter with a lambda, the direct way is to make the lambda mutable and use init-capture:

auto inc = [i=0]() mutable { return i++; };

The mutable is required because lambdas are const by default, and we need to directly modify the member i.


The above is C++14 (due to the generalized init-capture). We could make this work in C++11 by simply changing the nested-lambda structure from the inner lambda capturing by reference to capturing by value:

auto inc = []{ int i = 0; return [=]() mutable { return i++; }; }();
//                               ~~~   ~~~~~~~

That's... kind of obnoxious, but it works?

Related