Menggunakan variabel anggota dalam daftar tangkap lambda di dalam fungsi anggota

145

Kode berikut dikompilasi dengan gcc 4.5.1 tetapi tidak dengan 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;
}

Ini kesalahannya:

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

Begitu,

1> kompiler mana yang benar?

2> Bagaimana saya bisa menggunakan variabel anggota di dalam lambda di VS2010?

vivek
sumber
1
Catatan: Seharusnya pair<const int, set<int> >, itulah tipe pasangan sebenarnya dari peta. Mungkin juga harus menjadi referensi-ke-const.
Xeo
Terkait; sangat membantu: thispointer.com/...
Gabriel Staples

Jawaban:

157

Saya percaya VS2010 benar kali ini, dan saya akan memeriksa apakah saya memiliki standar yang berguna, tetapi saat ini saya tidak.

Sekarang, persis seperti pesan kesalahan mengatakan: Anda tidak dapat menangkap hal-hal di luar cakupan melampirkan lambda. grid tidak ada dalam lingkup terlampir, tetapi this(setiap akses untuk gridbenar - benar terjadi seperti this->griddalam fungsi anggota). Untuk penggunaan kami, menangkap thiskarya, karena Anda akan segera menggunakannya dan Anda tidak ingin menyalinnyagrid

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

Namun, jika Anda ingin menyimpan kisi dan menyalinnya untuk akses nanti, di mana puzzleobjek Anda mungkin sudah dihancurkan, Anda harus membuat salinan lokal menengah:

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

† Saya menyederhanakan - Google untuk "jangkauan" atau lihat §5.1.2 untuk semua detail berdarah.

Xeo
sumber
1
Tampaknya sangat terbatas pada saya. Saya tidak mengerti mengapa kompiler perlu mencegah hal seperti itu. Ini berfungsi baik dengan bind, meskipun sintaksnya mengerikan dengan operator shift kiri ostream.
Jean-Simon Brochu
3
Bisa tmpmenjadi const &untuk griduntuk mengurangi menyalin? Kami masih menginginkan setidaknya satu salinan, salinan ke lambda ( [tmp]), tetapi tidak perlu salinan kedua.
Aaron McDaid
4
Solusinya mungkin membuat salinan tambahan yang tidak perlu gridmeskipun mungkin dioptimalkan. Lebih pendek dan lebih baik adalah: auto& tmp = grid;dll.
Tom Swirly
4
Jika Anda memiliki C ++ 14 yang tersedia, Anda bisa lakukan [grid = grid](){ std::cout << grid[0][0] << "\n"; }untuk menghindari salinan tambahan
sigy
Tampaknya diperbaiki dalam gcc 4.9 (dan gcc 5.4 dalam hal ini)error: capture of non-variable ‘puzzle::grid’
BGabor
108

Ringkasan alternatif:

tangkap this:

auto lambda = [this](){};

gunakan referensi lokal untuk anggota:

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

contoh: https://godbolt.org/g/dEKVGD

Trass3r
sumber
5
Menarik bahwa hanya menggunakan penangkapan secara eksplisit dengan sintaks initializer berfungsi untuk ini (yaitu dalam C ++ 14 hanya melakukan [&grid]masih tidak bekerja). Sangat senang mengetahui hal ini!
ohruunuruus
1
Ringkasan yang bagus. Saya menemukan sintaks C ++ 14 sangat mudah
tuket
22

Saya percaya, Anda perlu menangkap this.

Michael Krelin - hacker
sumber
1
Ini benar, ini akan menangkap pointer ini dan Anda masih bisa langsung merujuk grid. Masalahnya, bagaimana jika Anda ingin menyalin kotak? Ini tidak akan memungkinkan Anda untuk melakukan itu.
Xeo
9
Anda dapat, tetapi hanya secara tidak langsung: Anda harus membuat salinan lokal, dan menangkap bahwa dalam lambda. Itu hanya aturan dengan lambdas, Anda tidak dapat menangkap kaku di luar lingkup terlampir.
Xeo
Tentu Anda bisa menyalin. Maksud saya, Anda tidak dapat menyalin-menangkapnya, tentu saja.
Michael Krelin - hacker
Apa yang saya jelaskan tidak menangkap salinan, melalui salinan lokal perantara - lihat jawaban saya. Selain itu, saya tidak tahu cara menyalin salinan variabel anggota.
Xeo
Tentu, itu menangkap salinan, tetapi bukan anggota. Ini melibatkan dua salinan kecuali jika kompiler lebih pintar dari biasanya, saya kira.
Michael Krelin - hacker
14

Metode alternatif yang membatasi ruang lingkup lambda daripada memberikannya akses ke keseluruhan thisadalah dengan memberikan referensi lokal ke variabel anggota, misalnya

auto& localGrid = grid;
int i;
for_each(groups.cbegin(),groups.cend(),[localGrid,&i](pair<int,set<int>> group){
            i++;
            cout<<i<<endl;
   });
dlanod
sumber
Saya suka ide Anda: menggunakan variabel referensi palsu dan meneruskannya ke daftar tangkapan :)
Emadpres