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?


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.


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


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.


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

Saya suka ide Anda: menggunakan variabel referensi palsu dan meneruskannya ke daftar tangkapan :)
— Emadpres
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.