205

The following code compiles with gcc 4.5.1 but not with VS2010 SP1:

#include <iostream>
#include <vector>
#include <map>
#include <utility>
#include <set>
#include <algorithm>
 
using namespace std;
class puzzle
{
    vector<vector<int>> grid;
    map<int,set<int>> groups;
public:
    int member_function();
};
 
int puzzle::member_function()
{
    int i;
    for_each(groups.cbegin(), groups.cend(), [grid, &i](pair<int,set<int>> group) {
        i++;
        cout << i << endl;
    });
}

This is the error:

error C3480: 'puzzle::grid': a lambda capture variable must be from an enclosing function scope
warning C4573: the usage of 'puzzle::grid' requires the compiler to capture 'this' but the current default capture mode does not allow it
  1. Which compiler is right?
  2. How can I use data members inside a lambda in VS2010?
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
vivek
  • 4,951
  • 4
  • 25
  • 33

4 Answers4

198

Summary of the alternatives:

capture this:

auto lambda = [this](){};

use a local reference to the member:

auto& tmp = grid;
auto lambda = [ tmp](){}; // capture grid by (a single) copy
auto lambda = [&tmp](){}; // capture grid by ref

C++14:

auto lambda = [ grid = grid](){}; // capture grid by copy
auto lambda = [&grid = grid](){}; // capture grid by ref

example: https://godbolt.org/g/dEKVGD

Trass3r
  • 5,858
  • 2
  • 30
  • 45
  • 10
    Interesting that only explicit use of the capture with initializer syntax works for this (i.e. in C++14 just doing `[&grid]` still doesn't work). Very glad to know this! – mattsilver Feb 10 '17 at 15:53
  • 5
    Good summary. I find the C++14 syntax very convenient – tuket Jul 22 '18 at 12:39
  • Note that the variable name can be anything: `auto lambda = [&anyName = grid](){};` – starriet Mar 16 '23 at 06:05
197

I believe VS2010 to be right this time, and I'd check if I had the standard handy, but currently I don't.

Now, it's exactly like the error message says: You can't capture stuff outside of the enclosing scope of the lambda. grid is not in the enclosing scope, but this is (every access to grid actually happens as this->grid in member functions). For your usecase, capturing this works, since you'll use it right away and you don't want to copy the grid

auto lambda = [this](){ std::cout << grid[0][0] << "\n"; }

If however, you want to store the grid and copy it for later access, where your puzzle object might already be destroyed, you'll need to make an intermediate, local copy:

vector<vector<int> > tmp(grid);
auto lambda = [tmp](){}; // capture the local copy per copy

† I'm simplifying - Google for "reaching scope" or see §5.1.2 for all the gory details.

Xeo
  • 129,499
  • 52
  • 291
  • 397
  • 1
    It seems quite limited to me. I don't understand why a compiler would need to prevent such a thing. It works well with bind, although the syntax is horrible with the ostream left shift operator. – Jean-Simon Brochu Nov 27 '13 at 14:53
  • 3
    Could `tmp` be a `const &` to `grid` to cut down on copying? We still want at least one copy, the copy into the lambda (`[tmp]`), but no need for a second copy. – Aaron McDaid Jul 28 '15 at 15:05
  • 4
    The solution might make an unnecessary extra copy of `grid` though it probably gets optimized out. Shorter and better is: `auto& tmp = grid;` etc. – Tom Swirly Aug 10 '15 at 17:49
  • 6
    If you have C++14 available, you could do `[grid = grid](){ std::cout << grid[0][0] << "\n"; }` to avoid the extra copy – sigy Mar 18 '16 at 11:49
  • It seems to be fixed in gcc 4.9 ( and gcc 5.4 for that matter) `error: capture of non-variable ‘puzzle::grid’` – BGabor Nov 29 '19 at 14:27
23

I believe, you need to capture this.

Michael Krelin - hacker
  • 138,757
  • 24
  • 193
  • 173
  • 1
    This is correct, it will capture the this-pointer and you can still just refer to `grid` directly. Problem being, what if you want to copy the grid? This won't allow you to do that. – Xeo Oct 25 '11 at 21:19
  • 10
    You can, but only in a roundabout way: You have to make a local copy, and capture *that* in the lambda. That's just the rule with lambdas, you can't capture stiff outside of the enclosing scope. – Xeo Oct 25 '11 at 21:30
  • Sure you can copy. I meant you can't copy-capture it, of course. – Michael Krelin - hacker Oct 25 '11 at 21:40
  • What I described does a copy capture, through the intermediate local copy - see my answer. Aside from that, I don't know any way to copy capture a member variable. – Xeo Oct 25 '11 at 21:42
  • Sure, it does copy capture, but not the member. It involves two copies unless compiler is smarter than usual, I'd guess. – Michael Krelin - hacker Oct 25 '11 at 21:52
  • We can get pedantic that this can be seen as a copy capture of the member, but lets just say that we both mean the same thing, ok? :) – Xeo Oct 25 '11 at 22:00
  • I think we both mean the same thing for quite a while now :) – Michael Krelin - hacker Oct 25 '11 at 22:02
  • For "capturing that", see my answer here, which is in an entirely different answer and context but applies just as well here (just ignore the async part): https://stackoverflow.com/a/42193934/1599699 – Andrew May 29 '17 at 04:45
16

An alternate method that limits the scope of the lambda rather than giving it access to the whole this is to pass in a local reference to the member variable, e.g.

auto& localGrid = grid;
int i;
for_each(groups.cbegin(),groups.cend(),[localGrid,&i](pair<int,set<int>> group){
            i++;
            cout<<i<<endl;
   });
dlanod
  • 8,664
  • 8
  • 54
  • 96