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?


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.


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


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.


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

Podoba mi się twój pomysł: użycie fałszywej zmiennej referencyjnej i przekazanie jej do listy przechwytywania :)
Emadpres
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.