Używanie zmiennej składowej na liście przechwytywania lambda wewnątrz funkcji składowej

145

Poniższy kod kompiluje się z gcc 4.5.1, ale nie z 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;
}

To jest błąd:

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

Więc,

1> który kompilator ma rację?

2> Jak mogę używać zmiennych składowych wewnątrz lambdy w VS2010?

vivek
źródło
1
Uwaga: Powinno być pair<const int, set<int> >, to jest rzeczywisty typ mapy. Prawdopodobnie powinno to być również odniesienie do stałej.
Xeo,
Związane z; bardzo pomocne: thispointer.com/…
Gabriel Staples

Odpowiedzi:

157

Uważam, że tym razem VS2010 ma rację i sprawdziłbym, czy mam pod ręką standard, ale obecnie nie mam.

Teraz jest dokładnie tak, jak w komunikacie o błędzie: Nie możesz przechwytywać rzeczy poza otaczającym zakresem lambda. grid nie znajduje się w zakresie obejmującym, ale thisjest (każdy dostęp do gridfaktycznie odbywa się tak, jak this->gridw przypadku funkcji składowych). Przechwytywanie thisdziała w Twoim przypadku , ponieważ będziesz go używać od razu i nie chcesz kopiować plikugrid

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

Jeśli jednak chcesz zachować siatkę i skopiować ją do późniejszego dostępu, gdzie Twój puzzleobiekt może już zostać zniszczony, musisz wykonać pośrednią, lokalną kopię:

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

† Upraszczam - Google dla „osiągnięcia zakresu” lub zobacz §5.1.2, aby poznać wszystkie krwawe szczegóły.

Xeo
źródło
1
Wydaje mi się to dość ograniczone. Nie rozumiem, dlaczego kompilator miałby temu zapobiegać. Działa dobrze z bind, chociaż składnia jest okropna z operatorem przesunięcia w lewo ostream.
Jean-Simon Brochu
3
Mogłyby tmpbyć const &do gridwyrąbać na kopiowanie? Nadal chcemy mieć co najmniej jedną kopię, kopię do lambda ( [tmp]), ale nie potrzebujemy drugiej kopii.
Aaron McDaid
4
Rozwiązanie może utworzyć niepotrzebną dodatkową kopię programu, gridchociaż prawdopodobnie zostanie zoptymalizowane. Krótsze i lepsze jest: auto& tmp = grid;itd.
Tom Swirly
4
Jeśli masz dostępny C ++ 14, możesz zrobić, [grid = grid](){ std::cout << grid[0][0] << "\n"; }aby uniknąć dodatkowej kopii
sigy
Wydaje się, że jest to naprawione w gcc 4.9 (i gcc 5.4 w tym przypadku)error: capture of non-variable ‘puzzle::grid’
BGabor
108

Podsumowanie alternatyw:

przechwytywanie this:

auto lambda = [this](){};

użyj lokalnego odniesienia do członka:

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

przykład: https://godbolt.org/g/dEKVGD

Trass3r
źródło
5
Ciekawe, że tylko jawne użycie przechwytywania ze składnią inicjalizatora działa w tym celu (tj. W C ++ 14 po prostu [&grid]nie działa). Bardzo się cieszę, że to wiem!
ohruunuruus,
1
Dobre podsumowanie. Uważam, że składnia C ++ 14 jest bardzo wygodna
tuket
22

Uważam, że musisz złapać this.

Michael Krelin - haker
źródło
1
To jest poprawne, przechwyci ten wskaźnik i nadal możesz po prostu odwołać się gridbezpośrednio. Problem polega na tym, a co jeśli chcesz skopiować siatkę? To nie pozwoli ci tego zrobić.
Xeo,
9
Można, ale tylko w okrężny sposób: trzeba zrobić kopię lokalną i przechwytywanie że w lambda. To tylko zasada dotycząca lambd, nie można wychwytywać sztywności poza zakresem obejmującym.
Xeo
Jasne, że możesz skopiować. Chodziło mi o to, że oczywiście nie można go skopiować-przechwycić.
Michael Krelin - haker
To, co opisałem, przechwytuje kopię, poprzez pośrednią kopię lokalną - zobacz moją odpowiedź. Poza tym nie znam żadnego sposobu na skopiowanie przechwytywania zmiennej składowej.
Xeo,
Jasne, kopiuje przechwytywanie, ale nie członka. Chyba wymaga dwóch kopii, chyba że kompilator jest mądrzejszy niż zwykle.
Michael Krelin - haker
14

Alternatywną metodą, która ogranicza zakres lambdy zamiast dawania jej dostępu do całości, thisjest przekazanie lokalnego odwołania do zmiennej składowej, np.

auto& localGrid = grid;
int i;
for_each(groups.cbegin(),groups.cend(),[localGrid,&i](pair<int,set<int>> group){
            i++;
            cout<<i<<endl;
   });
dlanod
źródło
Podoba mi się twój pomysł: użycie fałszywej zmiennej referencyjnej i przekazanie jej do listy przechwytywania :)
Emadpres