Lambda fonksiyonunun değişken değişkenin bir referanstan global değişkene davranış davranışı farkı


22

Mutable anahtar kelime ile küresel değişkene bir referans yakalamak için bir lambda kullanır ve sonra lambda işlevindeki değeri değiştirmek sonuçları derleyiciler arasında farklı bulundu.

#include <stdio.h>
#include <functional>

int n = 100;

std::function<int()> f()
{
    int &m = n;
    return [m] () mutable -> int {
        m += 123;
        return m;
    };
}

int main()
{
    int x = n;
    int y = f()();
    int z = n;

    printf("%d %d %d\n", x, y, z);
    return 0;
}

VS 2015 ve GCC'nin sonucu (g ++ (Ubuntu 5.4.0-6ubuntu1 ~ 16.04.12) 5.4.0 20160609):

100 223 100

Clang ++ 'ın sonucu (clang sürüm 3.8.0-2ubuntu4 (tags / RELEASE_380 / final)):

100 223 223

Bu neden oluyor? Buna C ++ Standartları izin veriyor mu?


Clang'ın davranışları gövde üzerinde hala var.
ceviz

Bunların hepsi oldukça eski derleyici versiyonlarıdır
MM


1
Yakalamayı tamamen kaldırırsanız, GCC yine de bu kodu kabul eder ve clang'ın yaptığı şeyi yapar. Bu, bir GCC hatası olduğu için güçlü bir ipucu - basit yakalamaların lambda gövdesinin anlamını değiştirmesi beklenmiyor.
TC

Yanıtlar:


16

Bir lambda referansı kendisini değere göre yakalayamaz ( std::reference_wrapperbu amaçla kullanın ).

Lambda'nızda, değere göre [m]yakalar m( &yakalamada hiç olmadığı için), bu yüzden m(bir referans olmak n) ilk önce kaydı kaldırılır ve referansta bulunduğu şeyin bir kopyası ( n) yakalanır. Bu, bunu yapmaktan farklı değildir:

int &m = n;
int x = m; // <-- copy made!

Sonra lambda orijinali değil, o kopyayı değiştirir. Beklediğiniz gibi VS ve GCC çıkışlarında gördüğünüz şey budur.

Clang çıktısı yanlıştır ve henüz yoksa bir hata olarak bildirilmelidir.

Eğer değiştirmek lambda istiyorsanız n, yakalama myerine referans olarak: [&m]. Bu, bir referansı diğerine atamaktan farklı değildir, örneğin:

int &m = n;
int &x = m; // <-- no copy made!

Veya, sadece kurtulabilirsiniz mtamamen ve yakalama nreferans olarak yerine: [&n].

Her ne kadar, nküresel kapsamda olduğu için, gerçekten yakalanması gerekmiyor olsa da, lambda onu yakalamadan küresel olarak erişebilir:

return [] () -> int {
    n += 123;
    return n;
};

5

Bence Clang aslında doğru olabilir.

Göre [lambda.capture] / 11 , bir kimlik-sentezleme bir teşkil yalnızca lambda kullanılan Lambda ile kopye yakalanan elemanına değinmektedir ODR kullanımını . Değilse, orijinal varlığı ifade eder . Bu, C ++ 11'den beri tüm C ++ sürümleri için geçerlidir.

C ++ 17'nin [basic.dev.odr] / 3'e göre, bir referans değişken, lvalve -rvalue dönüşümü uygulandığında sabit bir ifade verirse odr kullanılmaz.

Ancak C ++ 20 taslağında, lvalue-rvalue dönüşümü için gereksinim bırakılır ve ilgili geçiş, dönüşümü dahil etmek veya içermemek için birkaç kez değiştirilir. Bkz. CWG sayı 1472 ve CWG sayı 1741 ve ayrıca açık CWG sayı 2083 .

Yana mkullanılarak (statik depolama süresi nesneye atıfta) sabit bir ifade ile başlatılır Bu özel durum başına sabit bir ifade verir [expr.const] /2.11.1 .

Değer değeri nsabit bir ifadede kullanılamadığından , değer-değer-değer dönüşümleri uygulanırsa durum böyle değildir.

Bu nedenle, odda kullanımının belirlenmesinde lvalue-rvalue dönüşümlerinin uygulanıp uygulanmayacağına bağlı olarak, lambda'da kullandığınızda, mlambda üyesine işaret edebilir veya etmeyebilir.

Dönüştürmenin uygulanması gerekiyorsa, GCC ve MSVC doğrudur, aksi takdirde Clang doğrudur.

Artık ilk mifadesini sabit bir ifade olmayacak şekilde değiştirirseniz Clang'ın davranışını değiştirdiğini görebilirsiniz :

#include <stdio.h>
#include <functional>

int n = 100;

void g() {}

std::function<int()> f()
{
    int &m = (g(), n);
    return [m] () mutable -> int {
        m += 123;
        return m;
    };
}

int main()
{
    int x = n;
    int y = f()();
    int z = n;

    printf("%d %d %d\n", x, y, z);
    return 0;
}

Bu durumda tüm derleyiciler çıktının

100 223 100

çünkü mlambda tipi olan kapatma mekanizmasını en elemanına sevk edecektir içinde intreferans değişkeninden kopya-başlatıldı miçinde f.


VS / GCC ve Clang sonuçlarının her ikisi de doğru mu? Yoksa bunlardan sadece biri mi?
Willy

[basic.dev.odr] / 3 değişkenine lvalue m-rvalue dönüşümü uygulanmadan sabit bir ifade olmazsa değişkenin isimlendirdiği bir ifade tarafından odr kullanıldığını söylüyor . [Expr.const] / (2.7) uyarınca, bu dönüşüm çekirdek sabit bir ifade olmayacaktır.
aschepler

Clang'ın sonucu doğruysa, bence bir şekilde mantıksız. Çünkü programcının bakış açısından, yakalama listesine yazdığı değişkenin gerçekten değiştirilebilir bir durum için kopyalandığından ve m'nin başlatılmasının daha sonra bir nedenle programcı tarafından değiştirilebildiğinden emin olması gerekir.
Willy

1
m += 123;İşte modr kullanılır.
Oliv

1
Bence Clang şu anki ifadelere göre haklı ve buna girmeme rağmen, burada ilgili değişiklikler neredeyse tüm DR'ler.
TC

4

Buna C ++ 17 Standardı tarafından izin verilmez, ancak diğer bazı Standart taslaklar tarafından izin verilebilir. Bu cevapta açıklanmayan nedenlerle karmaşık.

[expr.prim.lambda.capture] / 10 :

Kopyala yakalanan her varlık için, kapatma türünde adlandırılmamış statik olmayan bir veri üyesi bildirilir. Bu üyelerin beyan düzenleri açıklanmamıştır. Varlık bir nesneye başvuru ise, böyle bir veri üyesinin türü başvurulan tür, eğer varlık bir işleve başvuru ise başvurulan işlev türüne bir lvalue referansı ya da aksi takdirde karşılık gelen yakalanan varlığın türü olur.

[m]Değişken demek olduğunu mhalinde fkopya tarafından yakalanır. Varlık m, nesneye bir başvuru olduğundan, kapatma türünün, başvurulan tür olan bir üyesi vardır. Yani üyenin türü, intdeğil int&.

Adı yana mlambda vücut isimlerin içindeki kapak nesnenin üyesi değil değişken f(ve bu tartışmalı parçasıdır), deyim m += 123;değiştirir farklı olduğunu üyesi intnesne ::n.

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.