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


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 для всех кровавых деталей.


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


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;
   });

Мне нравится ваша идея: использовать поддельную ссылочную переменную и передать ее в список захвата :)
Emadpres
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.