Lambda yakalama ve aynı isimli parametre - diğerini kim gölgeliyor? (clang 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 ve daha yeni çıktı "Clang ++ kullanıyorsunuz!" ve kullanılmayan yakalama konusunda uyarın foo.

  • g ++ 4.9.0 ve daha yenisi "g ++ kullanıyorsunuz!" ve kullanılmayan parametre konusunda uyarın foo.

Burada C ++ Standardını hangi derleyici daha doğru takip ediyor?

değnek kutusu örneği


1
Kodu wandbox'tan buraya yapıştırmak (paylaş düğmesini unutmuş gibi görünüyorlar) VS2015 (?) Clang'ın C4458 uyarısı ile aynı fikirde olmasına neden oluyor: 'foo' bildirimi sınıf üyesini gizler .
nwp

12
Harika örnek ..
deviantfan

4
Lambda, şablon işlev çağrı operatörüne sahip bir türe sahiptir, bu nedenle mantık, parametrenin yakalanan değişkeni içerideymiş gibi gölgelemesi gerektiğini söylememi sağlar struct Lambda { template<typename T> void operator()(T foo) const { /* ... */ } private: decltype(outer_foo) foo{outer_foo}; }.
skypjack

2
@nwp VS yanlış, lambda'nın veri üyeleri adsız ve bu nedenle gölgelenemez. Standart, "yakalanan bir varlığa erişimin karşılık gelen veri üyesine erişime dönüştürüldüğünü" söyler ve bu da bizi kare birde bırakır.
n. zamirler 'm.

10
Umarım clang sürümü doğrudur - bir işlevin dışındaki bir şey işlev parametresini tersine çevirmek yerine gölgelerse, yeni bir çığır açmış olur!
MM

Yanıtlar:


65

Güncelleme: Alt alıntıda Çekirdek başkanının söz verdiği gibi, kod artık biçimsiz :

Bir Eğer tanımlayıcı bir de basit bir yakalama olarak görünür Bildiricisi-id bir parametrenin lambda Bildiricisi sitesindeki parametre beyanı-madde , bir program kötü oluşturulur.


Bir süre önce lambdalarda isim arama ile ilgili birkaç sorun vardı. N2927 ile çözüldü :

Yeni ifade, yakalanan varlıkların kullanımlarını yeniden eşleştirmek için artık aramaya dayanmamaktadır. Bir lambda'nın bileşik ifadesinin iki geçişte işlendiği veya bu bileşik ifadesindeki herhangi bir adın kapanış türünün bir üyesine çözümlenebileceği yorumlarını daha açık bir şekilde reddeder .

Arama her zaman lambda ifadesi bağlamında yapılır , hiçbir zaman bir kapatma türünün üye işlev gövdesine dönüşümden "sonra" yapılmaz . Bkz. [Expr.prim.lambda] / 8 :

Lambda-ifadenin 'in bileşiği ile ifade verir fonksiyonu gövdesi işlev çağrısı operatörün ([dcl.fct.def]), ancak adı arama amacıyla, [...], bileşik-ifadesi bağlamında düşünülmektedir lambda-sentezleme . [ Örnek :

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

- son örnek ]

(Örnek ayrıca, aramanın, kapama türünün oluşturulan yakalama üyesini bir şekilde dikkate almadığını açıkça ortaya koymaktadır.)

Ad foo, yakalamada (yeniden) beyan edilmemiştir; lambda ifadesini çevreleyen blokta bildirilmiştir. Parametre foo, bu dış blokta yer alan bir blokta bildirilir ( ayrıca lambda parametrelerinden de açıkça bahseden [basic.scope.block] / 2'ye bakın ). Arama sırası açıkça iç bloklardan dışa doğru doğrudur . Dolayısıyla parametre seçilmelidir, yani Clang doğrudur.

Yakalamayı bir başlatma-yakalama yapacak olsaydınız , yani foo = ""bunun yerine foo, cevap net olmazdı. Bunun nedeni, yakalamanın artık aslında "bloğu" verilmeyen bir bildirimi tetiklemesidir. Bu konuda çekirdek sandalyeye mesaj attım, kim cevapladı

Bu 2211 numaralı sayıdır (kısa bir süre sonra open-std.org sitesinde yeni bir sayı listesi görünecek, maalesef bu sorunlardan biri olan birkaç sorun için yer tutucularla birlikte; Kona'dan önce bu boşlukları doldurmak için çok çalışıyorum ayın sonunda toplantı). CWG bunu Ocak telekonferansımızda tartıştı ve yön, eğer bir yakalama adı da bir parametre adı ise, programı kötü biçimlendirmek.


Burada parçalamam için hiçbir şey yok :) Basit yakalama hiçbir şey bildirmez, bu nedenle ad aramasının doğru sonucu oldukça açıktır (BTW, GCC , açık yakalama yerine bir yakalama varsayılanı kullanırsanız bunu doğru yapar ). init-capture s biraz daha yanıltıcıdır.
TC

1
@TC Katılıyorum. Temel bir konu açtım, ancak görünüşe göre bu daha önce tartışılmış, düzenlenmiş cevaba bakınız.
Columbo

6

Size anlamlı bir cevap vermek için soruya birkaç yorumu bir araya getirmeye çalışıyorum.
Her şeyden önce şunu unutmayın:

  • Statik olmayan veri üyeleri, her kopya yakalanan değişken için lambda için bildirilir
  • Spesifik durumda, lambda, adında bir parametreyi kabul eden genel bir satır içi şablon işlevi çağrı operatörüne sahip bir kapatma türüne sahiptir. foo

Bu nedenle mantık, ilk bakışta parametrenin, yakalanan değişkeni şunun içindeymiş gibi gölgelemesi gerektiğini söylememi sağlar:

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

Her neyse, @nm, kopya yakalanan değişkenler için bildirilen statik olmayan veri üyelerinin aslında adsız olduğunu doğru bir şekilde belirtti. Bununla birlikte, adsız veri üyesine bir tanımlayıcı (yani foo) aracılığıyla erişilir . Bu nedenle, işlev çağrısı operatörünün parametre adı yine de (diyeyim) bu tanımlayıcıyı gölgelemelidir .
Soruya yapılan yorumlarda @nm tarafından doğru bir şekilde işaret edildiği gibi:

orijinal yakalanan varlık [...] kapsam kurallarına göre normal şekilde gölgelenmelidir

Bu nedenle, clang'ın doğru olduğunu söyleyebilirim.


Yukarıda açıklandığı gibi, bu bağlamda arama asla dönüştürülmüş kapatma tipindeymişiz gibi gerçekleştirilmez.
Columbo

@Columbo Sebepten açık olsa bile kaçırdığım bir satır ekliyorum, yani clang doğru. İşin komik yanı, bir cevap vermeye çalışırken [expr.prim.lambda] / 8'i bulmuş olmam, ama sizin yaptığınız gibi doğru şekilde kullanamadım. Bu yüzden her seferinde cevaplarınızı okumak bir zevktir. ;-)
skypjack
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.