Saya menemukan hasilnya berbeda di kompiler jika saya menggunakan lambda untuk menangkap referensi ke variabel global dengan kata kunci yang bisa berubah dan kemudian memodifikasi nilai dalam fungsi lambda.
#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;
}
Hasil dari VS 2015 dan GCC (g ++ (Ubuntu 5.4.0-6ubuntu1 ~ 16.04.12) 5.4.0 20160609):
100 223 100
Hasil dari dentang ++ (dentang versi 3.8.0-2ubuntu4 (tag / RELEASE_380 / final)):
100 223 223
Mengapa ini terjadi? Apakah ini diizinkan oleh Standar C ++?
c++
c++11
lambda
language-lawyer
Willy
sumber
sumber
Jawaban:
Lambda tidak dapat menangkap referensi itu sendiri berdasarkan nilai (digunakan
std::reference_wrapper
untuk tujuan itu).Di lambda Anda,
[m]
menangkapm
dengan nilai (karena tidak ada&
dalam penangkapan), jadim
(menjadi referensin
) pertama kali direferensikan dan salinan dari hal yang direferensikan (n
) ditangkap. Ini tidak berbeda dengan melakukan ini:Lambda kemudian memodifikasi salinan itu, bukan yang asli. Itulah yang Anda lihat terjadi dalam output VS dan GCC, seperti yang diharapkan.
Output dentang salah, dan harus dilaporkan sebagai bug, jika belum.
Jika Anda ingin lambda Anda untuk memodifikasi
n
, capturem
dengan referensi sebagai gantinya:[&m]
. Ini tidak berbeda dengan menugaskan satu referensi ke referensi lain, misalnya:Atau, Anda hanya dapat menyingkirkan
m
sama sekali dan menangkapn
dengan referensi sebagai gantinya:[&n]
.Meskipun, karena
n
berada dalam lingkup global, itu benar-benar tidak perlu ditangkap sama sekali, lambda dapat mengaksesnya secara global tanpa menangkapnya:sumber
Saya pikir Dentang mungkin benar.
Menurut [lambda.capture] / 11 , ekspresi-id yang digunakan dalam lambda mengacu pada anggota yang diambil oleh-lambda hanya jika itu merupakan penggunaan odr . Jika tidak, maka mengacu pada entitas asli . Ini berlaku untuk semua versi C ++ sejak C ++ 11.
Menurut C ++ 17 [basic.dev.odr] / 3 variabel referensi tidak digunakan jika menggunakan konversi lvalue-ke-rvalue akan menghasilkan ekspresi yang konstan.
Namun dalam konsep C ++ 20 persyaratan untuk konversi nilai-ke-nilai turun dan bagian yang relevan diubah beberapa kali untuk menyertakan atau tidak menyertakan konversi. Lihat masalah CWG 1472 dan masalah CWG 1741 , serta masalah CWG terbuka 2083 .
Karena
m
diinisialisasi dengan ekspresi konstan (merujuk ke objek durasi penyimpanan statis), menggunakannya menghasilkan ekspresi konstan per pengecualian dalam [expr.const] /2.11.1 .Namun, ini bukan masalahnya jika konversi nilai-ke-nilai diterapkan, karena nilai
n
tidak dapat digunakan dalam ekspresi konstan.Oleh karena itu, tergantung pada apakah konversi nilai-ke-nilai atau tidak seharusnya diterapkan dalam menentukan penggunaan odr, ketika Anda menggunakannya
m
di lambda, itu mungkin merujuk pada anggota lambda atau tidak.Jika konversi harus diterapkan, GCC dan MSVC sudah benar, jika tidak, Dentang adalah.
Anda dapat melihat bahwa Dentang mengubah perilaku itu jika Anda mengubah inisialisasi
m
menjadi tidak ekspresi konstan lagi:Dalam hal ini semua kompiler setuju bahwa outputnya adalah
karena
m
dalam lambda akan merujuk ke anggota penutupan yang merupakan jenisint
salin diinisialisasi dari variabel referensim
dif
.sumber
m
tersebut odr-digunakan oleh ekspresi penamaan kecuali jika menerapkan konversi lvalue-ke-rvalue akan menjadi ekspresi konstan. Dengan [expr.const] / (2.7), konversi itu tidak akan menjadi ekspresi konstanta inti.m += 123;
Di sinim
digunakan odr.Ini tidak diizinkan oleh Standar C ++ 17, tetapi oleh beberapa konsep Standar lain mungkin. Ini rumit, karena alasan yang tidak dijelaskan dalam jawaban ini.
[expr.prim.lambda.capture] / 10 :
The
[m]
berarti bahwa variabelm
dalamf
ditangkap oleh copy. Entitasm
adalah referensi ke objek, jadi tipe penutupan memiliki anggota yang tipenya adalah tipe yang direferensikan. Yaitu, tipe anggota adalahint
, dan bukanint&
.Karena nama
m
di dalam tubuh lambda menamai anggota objek penutupan dan bukan variabel dif
(dan ini adalah bagian yang dipertanyakan), pernyataanm += 123;
memodifikasi anggota itu, yang merupakanint
objek yang berbeda::n
.sumber