Mengapa saya tidak bisa menangkap ini dengan referensi ('& ini') di lambda?

91

Saya memahami cara yang benar untuk menangkap this(untuk mengubah properti objek) di lambda adalah sebagai berikut:

auto f = [this] () { /* ... */ };

Tapi saya penasaran dengan keanehan berikut yang pernah saya lihat:

class C {
    public:
        void foo() {
            // auto f = [] () { // this not captured
            auto f = [&] () { // why does this work?
            // auto f = [&this] () { // Expected ',' before 'this'
            // auto f = [this] () { // works as expected
                x = 5;
            };
            f();
        }

    private:
        int x;
};

Keanehan yang membuat saya bingung (dan ingin dijawab) adalah mengapa berikut ini berhasil:

auto f = [&] () { /* ... */ }; // capture everything by reference

Dan mengapa saya tidak bisa secara eksplisit menangkap thisdengan referensi:

auto f = [&this] () { /* ... */ }; // a compiler error as seen above.
Anthony Sottile
sumber
6
Mengapa Anda ingin ? Dalam hal hal-hal di mana referensi ke pointer mungkin pernah berguna: thistidak dapat diubah, itu tidak cukup besar untuk membuat referensi lebih cepat ... dan bagaimanapun , itu tidak benar-benar ada , jadi itu telah tidak ada kehidupan nyata, yang berarti referensi apa pun padanya akan tergantung menurut definisi. thisadalah nilai pr, bukan lvalue.
underscore_d

Jawaban:

111

Alasan [&this]tidak berhasil adalah karena ini adalah kesalahan sintaks. Setiap parameter yang dipisahkan koma di lambda-introduceris a capture:

capture:
    identifier
    & identifier
    this

Anda dapat melihat itu &thistidak diperbolehkan secara sintaksis. Alasan itu tidak diizinkan adalah karena Anda tidak ingin menangkap thisdengan referensi, karena ini adalah penunjuk const kecil. Anda hanya ingin meneruskannya berdasarkan nilai - jadi bahasanya tidak mendukung penangkapan thisdengan referensi.

Untuk menangkap thissecara eksplisit Anda dapat menggunakan [this]file lambda-introducer.

Yang pertama capturebisa jadi capture-defaultadalah:

capture-default:
    &
    =

Ini berarti menangkap secara otomatis apa pun yang saya gunakan, masing-masing dengan referensi ( &) atau dengan nilai ( =) - namun perlakuannya thiskhusus - dalam kedua kasus itu ditangkap oleh nilai untuk alasan yang diberikan sebelumnya (bahkan dengan tangkapan default &, yang biasanya berarti ditangkap dengan referensi).

5.1.2.7/8:

Untuk tujuan pencarian nama (3.4), menentukan jenis dan nilai this(9.3.2) dan mengubah ekspresi id yang mengacu pada anggota kelas non-statis menjadi ekspresi akses anggota kelas menggunakan (*this)(9.3.1), pernyataan gabungan [OF THE LAMBDA] dianggap dalam konteks ekspresi lambda.

Jadi lambda bertindak seolah-olah itu adalah bagian dari fungsi anggota yang melingkupi saat menggunakan nama anggota (seperti dalam contoh Anda, penggunaan nama x), sehingga akan menghasilkan "penggunaan implisit" thisseperti fungsi anggota.

Jika lambda-capture menyertakan capture-default yaitu &, pengidentifikasi dalam lambda-capture tidak boleh didahului oleh &. Jika lambda-capture menyertakan capture-default yaitu =, lambda-capture tidak boleh berisi thisdan setiap pengenal yang dikandungnya harus diawali dengan &. Pengidentifikasi atau thistidak akan muncul lebih dari sekali dalam tangkapan lambda.

Sehingga Anda dapat menggunakan [this], [&], [=]atau [&,this]sebagai lambda-introduceruntuk menangkap thispointer dengan nilai.

Namun [&this]dan [=, this]berbentuk buruk. Dalam kasus terakhir, gcc dengan memaafkan memperingatkan hal [=,this]itu explicit by-copy capture of ‘this’ redundant with by-copy capture defaultdaripada kesalahan.

Andrew Tomazos
sumber
3
@KonradRudolph: Bagaimana jika Anda ingin menangkap beberapa hal berdasarkan nilai dan lainnya dengan referensi? Atau ingin sangat eksplisit dengan apa yang Anda tangkap?
Xeo
2
@KonradRudol: Ini adalah fitur keamanan. Anda mungkin tidak sengaja menangkap nama yang tidak Anda inginkan.
Andrew Tomazos
8
@KonradRudolph: Konstruksi tingkat blok tidak secara ajaib menyalin penunjuk ke objek yang mereka gunakan ke dalam tipe anonim tak terlihat baru yang kemudian dapat bertahan dari lingkup yang melingkupinya - cukup dengan menggunakan nama objek dalam ekspresi. Menangkap Lambda lebih merupakan bisnis yang berbahaya.
Andrew Tomazos
5
@KonradRudolph Saya akan mengatakan "gunakan [&]jika Anda melakukan sesuatu seperti membuat blok untuk diteruskan ke struktur kontrol", tetapi secara eksplisit menangkap jika Anda memproduksi lambda yang akan digunakan untuk tujuan yang kurang sederhana. [&]adalah ide yang buruk jika lambda akan hidup lebih lama dari jangkauan saat ini. Namun, banyak penggunaan lambda hanyalah cara untuk meneruskan blok ke struktur kontrol, dan blok tidak akan hidup lebih lama dari blok yang dibuat dalam cakupannya.
Yakk - Adam Nevraumont
2
@Ruslan: Tidak, thisadalah kata kunci, thisbukan pengenal.
Andrew Tomazos
6

Karena standar tidak ada &thisdalam daftar Captures:

N4713 8.4.5.2 Penangkapan:

lambda-capture:
    capture-default
    capture-list
    capture-default, capture-list

capture-default:
    &
    =
capture-list:
    capture...opt
    capture-list, capture...opt
capture:
    simple-capture
    init-capture
simple-capture:
    identifier
    &identifier
    this
    * this
init-capture:
    identifier initializer
    &identifier initializer
  1. Untuk tujuan pengambilan lambda, sebuah ekspresi berpotensi mereferensikan entitas lokal sebagai berikut:

    7.3 Ekspresi ini berpotensi mereferensikan * this.

Jadi, jaminan standar thisdan *thisvalid, dan &thistidak valid. Juga, menangkap thisberarti menangkap *this(yang merupakan nilai l, objek itu sendiri) dengan referensi , daripada menangkap thispenunjuk dengan nilai !

陳 力
sumber
*thismenangkap objek berdasarkan nilai
sp2danny