Использование переменной-члена в лямбда-списке захвата внутри функции-члена

145

Следующий код компилируется с gcc 4.5.1, но не с 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;
        });
}
int main()
{
        return 0;
}

Это ошибка:

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> какой компилятор прав?

2> Как я могу использовать переменные-члены внутри лямбды в VS2010?

Вивек
источник
1
Примечание. Должно быть pair<const int, set<int> >, это фактический тип пары карты. Возможно, это также должна быть ссылка на const.
Xeo
Связанный; очень полезно: thispointer.com/…
Габриэль Стейплс

Ответы:

157

Я считаю, что VS2010 будет правильным на этот раз, и я бы проверил, был ли у меня под рукой стандарт, но в настоящее время у меня нет.

Теперь это похоже на сообщение об ошибке: «Вы не можете захватывать вещи вне рамок лямбды. grid не входит в объем, но thisесть (каждый доступ к gridдействительности происходит как this->gridв функциях-членах). В вашем случае захват thisработает, так как вы будете использовать его сразу и не хотите копироватьgrid

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

Однако, если вы хотите сохранить сетку и скопировать ее для последующего доступа, где ваш puzzleобъект уже может быть уничтожен, вам нужно будет сделать промежуточную локальную копию:

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

† Я упрощаю - Google для "достижения области" или см. §5.1.2 для всех кровавых деталей.

Xeo
источник
1
Это кажется довольно ограниченным для меня. Я не понимаю, почему компилятор должен был бы предотвратить такую ​​вещь. Он хорошо работает с связыванием, хотя синтаксис ужасен с оператором левого сдвига ostream.
Жан-Симон Брошу
3
Может tmpбыть , const &чтобы gridсократить копирование? Мы все еще хотим по крайней мере одну копию, копию в lambda ( [tmp]), но нет необходимости во второй копии.
Аарон МакДейд
4
Решение может создать ненужную дополнительную копию, gridхотя, вероятно, оно будет оптимизировано. Короче и лучше: auto& tmp = grid;и т. Д.
Том Свирли
4
Если у вас есть C ++ 14, вы можете обойтись [grid = grid](){ std::cout << grid[0][0] << "\n"; }без дополнительной копии
sigy
Кажется, это исправлено в gcc 4.9 (и gcc 5.4 в этом отношении)error: capture of non-variable ‘puzzle::grid’
BGabor
108

Краткое изложение альтернатив:

захватить this:

auto lambda = [this](){};

используйте локальную ссылку на участника:

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

пример: https://godbolt.org/g/dEKVGD

Trass3r
источник
5
Интересно, что для этого работает только явное использование перехвата с синтаксисом инициализатора (т. Е. В C ++ 14 просто выполнение по- [&grid]прежнему не работает). Очень рад это знать!
ohruunuruus
1
Хорошее резюме. Я нахожу синтаксис C ++ 14 очень удобным
tuke
22

Я считаю, вам нужно захватить this.

Михаил Крелин - хакер
источник
1
Это правильно, он захватит указатель this, и вы все равно можете просто обратиться к нему gridнапрямую. Проблема в том, что если вы хотите скопировать сетку? Это не позволит вам сделать это.
Xeo
9
Можно, но только окольным путем: Вы должны сделать локальную копию, и захват , что в лямбда. Это просто правило с лямбдами, вы не можете захватить жесткие за пределами ограждающей области.
Xeo
Конечно, вы можете скопировать. Я имел в виду, что вы не можете скопировать его, конечно.
Майкл Крелин - хакер
То, что я описал, делает захват копии через промежуточную локальную копию - см. Мой ответ. Кроме того, я не знаю способа скопировать захват переменной-члена.
Xeo
Конечно, он делает копию захвата, но не член. Я предполагаю, что он включает в себя две копии, если компилятор не умнее обычного.
Майкл Крелин - хакер
14

Альтернативный метод, который ограничивает область действия лямбды, а не дает ей доступ ко всему, thisсостоит в передаче локальной ссылки на переменную-член, например

auto& localGrid = grid;
int i;
for_each(groups.cbegin(),groups.cend(),[localGrid,&i](pair<int,set<int>> group){
            i++;
            cout<<i<<endl;
   });
dlanod
источник
Мне нравится ваша идея: использовать поддельную ссылочную переменную и передать ее в список захвата :)
Emadpres