Tangkapan Lambda dan parameter dengan nama yang sama - siapa yang membayangi yang lain? (dentang vs gcc)


125
auto foo = "You're using g++!";
auto compiler_detector = [foo](auto foo) { std::puts(foo); };
compiler_detector("You're using clang++!");
  • clang ++ 3.6.0 dan yang lebih baru mencetak "Anda menggunakan clang ++!" dan peringatkan tentang tangkapan foo yang tidak digunakan.

  • g ++ 4.9.0 dan yang lebih baru mencetak "Anda menggunakan g ++!" dan peringatkan tentang parameter foo yang tidak digunakan.

Kompiler apa yang lebih akurat mengikuti Standar C ++ di sini?

contoh kotak tongkat


1
Menempelkan kode dari wandbox ke sini (mereka sepertinya lupa tombol bagikan) membuatnya tampak seperti VS2015 (?) Setuju dengan dentang peringatan C4458: deklarasi 'foo' menyembunyikan anggota kelas .
nwp

12
Contoh yang bagus ..
deviantfan

4
Lambda memiliki tipe dengan operator panggilan fungsi template, sehingga logikanya akan membuat saya mengatakan bahwa parameter harus membayangi variabel yang ditangkap seolah-olah dalam struct Lambda { template<typename T> void operator()(T foo) const { /* ... */ } private: decltype(outer_foo) foo{outer_foo}; }.
skypjack

2
@nwp VS salah, anggota data lambda tidak disebutkan namanya dan karenanya tidak dapat dibayangi. Standar mengatakan "akses ke entitas yang ditangkap diubah menjadi akses ke anggota data yang sesuai", yang membuat kita berada di titik awal.
n. 'kata ganti' m.

10
Saya berharap versi clang benar - ini akan menjadi terobosan baru jika sesuatu di luar fungsi membayangi parameter fungsi, bukan sebaliknya!
MM

Jawaban:


65

Pembaruan: seperti yang dijanjikan oleh kursi Inti di kutipan bawah, kodenya sekarang salah bentuk :

Jika identifier di sederhana-capture muncul sebagai deklarator-id dari parameter lambda-declarator 's parameter-deklarasi-klausul , program ini sakit-terbentuk.


Ada beberapa masalah tentang pencarian nama di lambda beberapa waktu lalu. Mereka diselesaikan oleh N2927 :

Kata-kata baru tidak lagi bergantung pada pencarian untuk memetakan ulang penggunaan entitas yang ditangkap. Ini lebih jelas menyangkal interpretasi bahwa pernyataan-majemuk lambda diproses dalam dua lewat atau bahwa nama apa pun dalam pernyataan-majemuk itu mungkin diselesaikan ke anggota tipe penutupan.

Pencarian selalu dilakukan dalam konteks ekspresi lambda , tidak pernah "setelah" transformasi ke badan fungsi anggota tipe closure. Lihat [expr.prim.lambda] / 8 :

The lambda ekspresi 's senyawa-pernyataan menghasilkan fungsi tubuh ([dcl.fct.def]) dari operator fungsi panggilan, tetapi untuk tujuan nama lookup, [...], yang senyawa-pernyataan dianggap dalam konteks yang lambda ekspresi . [ Contoh :

struct S1 {
  int x, y;
  int operator()(int);
  void f() {
    [=]()->int {
      return operator()(this->x+y);  // equivalent to: S1::operator()(this->x+(*this).y)
                                     // and this has type S1*
    }; 
  }
};

- contoh akhir ]

(Contoh ini juga memperjelas bahwa pencarian entah bagaimana tidak mempertimbangkan anggota penangkapan yang dihasilkan dari tipe penutupan.)

Nama footidak (kembali) dideklarasikan dalam penangkapan; itu dideklarasikan di blok yang menyertakan ekspresi lambda. Parameter foodideklarasikan dalam blok yang bertumpuk di blok luar itu (lihat [basic.scope.block] / 2 , yang juga secara eksplisit menyebutkan parameter lambda). Urutan pencarian jelas dari blok dalam ke blok luar . Karenanya parameter harus dipilih, yaitu, Clang benar.

Jika Anda adalah untuk membuat penangkapan init-capture, yaitu foo = ""bukan foo, jawabannya tidak akan jelas. Ini karena penangkapan sekarang benar-benar memicu deklarasi yang "blok" tidak diberikan. Saya mengirim pesan ke kursi inti tentang ini, yang menjawab

Ini adalah masalah 2211 (daftar masalah baru akan segera muncul di situs open-std.org, sayangnya hanya dengan placeholder untuk sejumlah masalah, yang salah satunya; Saya bekerja keras untuk mengisi celah tersebut sebelum Kona pertemuan di akhir bulan). CWG membahas hal ini selama telekonferensi Januari kami, dan arahannya adalah membuat program menjadi tidak bagus jika nama penangkap juga merupakan nama parameter.


Tidak ada yang bisa saya sobek di sini :) A simple-capture tidak mendeklarasikan apa-apa, jadi hasil yang benar dari pencarian nama cukup jelas (BTW, GCC melakukannya dengan benar jika Anda menggunakan capture-default dan bukan eksplisit capture). init-capture s agak lebih rumit.
TC

1
@TC Saya setuju. Saya mengajukan masalah inti, tetapi ternyata ini sudah dibahas, lihat jawaban yang diedit.
Columbo

6

Saya mencoba mengemas beberapa komentar untuk pertanyaan untuk memberi Anda jawaban yang berarti.
Pertama-tama, perhatikan bahwa:

  • Anggota data non-statis dideklarasikan untuk lambda untuk setiap variabel yang diambil salinannya
  • Dalam kasus tertentu, lambda memiliki tipe penutupan yang memiliki operator panggilan fungsi template inline publik yang menerima parameter bernama foo

Oleh karena itu, logikanya akan membuat saya mengatakan pada pandangan pertama bahwa parameter harus membayangi variabel yang ditangkap seolah-olah dalam:

struct Lambda {
    template<typename T> void operator()(T foo) const { /* ... */ }
    private: decltype(outer_foo) foo{outer_foo};
};

Bagaimanapun, @nm mencatat dengan benar bahwa anggota data non-statis yang dideklarasikan untuk variabel yang diambil salinan sebenarnya tidak bernama. Dengan demikian, anggota data yang tidak disebutkan namanya masih dapat diakses melalui pengidentifikasi (yaitu foo). Oleh karena itu, nama parameter dari operator panggilan fungsi masih harus (biarkan saya katakan) bayangan pengenal itu .
Seperti yang ditunjukkan dengan benar oleh @nm di komentar untuk pertanyaan:

entitas asli yang ditangkap [...] harus dibayangi secara normal sesuai dengan aturan cakupan

Karena itu, menurut saya dentang itu benar.


Seperti dijelaskan di atas, pencarian dalam konteks ini tidak pernah dilakukan seolah-olah kita berada dalam tipe penutupan yang diubah.
Columbo

@Columbo Saya menambahkan baris yang saya lewatkan meskipun jelas dari alasannya, bahwa dentang itu benar. Bagian yang lucu adalah saya menemukan [expr.prim.lambda] / 8 ketika mencoba memberikan jawaban, tetapi saya tidak dapat menggunakannya dengan baik seperti yang Anda lakukan. Itulah mengapa setiap kali senang membaca jawaban Anda. ;-)
skypjack
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.