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.