Pertanyaan ini tidak dapat dijawab sepenuhnya dalam kode. Anda mungkin dapat menulis kode yang agak "setara", tetapi standarnya tidak ditentukan seperti itu.
Dengan itu, mari selami [expr.prim.lambda]
. Hal pertama yang perlu diperhatikan adalah konstruktor hanya disebutkan dalam [expr.prim.lambda.closure]/13
:
Jenis penutupan terkait dengan lambda ekspresi tidak memiliki konstruktor default jika lambda ekspresi memiliki lambda-capture dan konstruktor default macet sebaliknya. Ini memiliki konstruktor salin default dan konstruktor pemindahan default ([class.copy.ctor]). Ini memiliki operator copy tugas dihapus jika lambda ekspresi memiliki lambda-capture dan macet copy dan operator penugasan bergerak sebaliknya ([class.copy.assign]). [ Catatan: Fungsi-fungsi anggota khusus ini secara implisit didefinisikan seperti biasa, dan karenanya dapat didefinisikan sebagai dihapus. - catatan akhir ]
Jadi, langsung kelelawar, harus jelas bahwa konstruktor tidak secara formal bagaimana menangkap objek didefinisikan. Anda bisa mendapatkan cukup dekat (lihat jawaban cppinsights.io), tetapi detailnya berbeda (perhatikan bagaimana kode dalam jawaban untuk kasus 4 tidak dikompilasi).
Ini adalah klausa standar utama yang diperlukan untuk membahas kasus 1:
[expr.prim.lambda.capture]/10
[...]
Untuk setiap entitas yang ditangkap melalui salinan, anggota data non-statis yang tidak disebutkan namanya dinyatakan dalam tipe penutupan. Urutan deklarasi anggota ini tidak ditentukan. Jenis anggota data tersebut adalah tipe yang direferensikan jika entitas merupakan referensi ke objek, referensi nilai untuk tipe fungsi yang direferensikan jika entitas tersebut merupakan referensi ke suatu fungsi, atau jenis entitas yang ditangkap yang sesuai sebaliknya. Seorang anggota serikat anonim tidak akan ditangkap melalui salinan.
[expr.prim.lambda.capture]/11
Setiap ekspresi-id di dalam senyawa-pernyataan dari ekspresi lambda yang merupakan penggunaan odr dari entitas yang ditangkap oleh salinan ditransformasikan menjadi akses ke anggota data yang tidak disebutkan namanya dari tipe penutupan. [...]
[expr.prim.lambda.capture]/15
Ketika ekspresi lambda dievaluasi, entitas yang ditangkap oleh salinan digunakan untuk menginisialisasi langsung setiap anggota data non-statis terkait dari objek penutupan yang dihasilkan, dan anggota data non-statis yang sesuai dengan init-captures diinisialisasi sebagai ditunjukkan oleh penginisialisasi yang sesuai (yang dapat berupa inisialisasi salin atau langsung). [...]
Mari kita terapkan ini pada kasus Anda 1:
Kasus 1: ditangkap dengan nilai / tangkapan standar dengan nilai
int x = 6;
auto lambda = [x]() { std::cout << x << std::endl; };
Tipe penutupan lambda ini akan memiliki anggota data non-statis yang tidak disebutkan namanya (sebut saja __x
) tipe int
(karena x
bukan merupakan referensi atau fungsi), dan akses ke x
dalam tubuh lambda ditransformasikan menjadi akses ke __x
. Ketika kami mengevaluasi ekspresi lambda (yaitu ketika menetapkan lambda
), kami langsung menginisialisasi __x
dengan x
.
Singkatnya, hanya satu salinan yang terjadi . Konstruktor tipe penutupan tidak terlibat, dan tidak mungkin untuk menyatakan ini dalam C ++ normal (perhatikan bahwa tipe penutupan juga bukan tipe agregat ).
Pengambilan referensi meliputi [expr.prim.lambda.capture]/12
:
Suatu entitas ditangkap dengan referensi jika secara implisit atau eksplisit ditangkap tetapi tidak ditangkap oleh salinan. Tidak ditentukan apakah anggota data non-statis tambahan yang tidak disebutkan namanya dinyatakan dalam tipe penutupan untuk entitas yang ditangkap oleh referensi. [...]
Ada paragraf lain tentang penangkapan referensi, tetapi kami tidak melakukannya di mana pun.
Jadi, untuk kasus 2:
Kasus 2: penangkapan dengan referensi / penangkapan standar dengan referensi
int x = 6;
auto lambda = [&x]() { std::cout << x << std::endl; };
Kami tidak tahu apakah anggota ditambahkan ke jenis penutupan. x
dalam tubuh lambda mungkin langsung merujuk ke x
luar. Ini tergantung pada kompilator untuk mencari tahu, dan itu akan melakukan ini dalam beberapa bentuk bahasa perantara (yang berbeda dari kompiler ke kompiler), bukan sumber transformasi dari kode C ++.
Pengambilan init dirinci dalam [expr.prim.lambda.capture]/6
:
Tangkapan init berperilaku seolah-olah mendeklarasikan dan secara eksplisit menangkap variabel dari bentuk auto init-capture ;
yang wilayah deklaratifnya adalah pernyataan gabungan lambda-ekspresi, kecuali bahwa:
- (6.1) jika penangkapan dilakukan dengan menyalin (lihat di bawah), anggota data non-statis yang dinyatakan untuk penangkapan dan variabel diperlakukan sebagai dua cara berbeda untuk merujuk ke objek yang sama, yang memiliki masa pakai data non-statis anggota, dan tidak ada salinan dan penghancuran tambahan dilakukan, dan
- (6.2) jika tangkapan adalah dengan referensi, masa hidup variabel berakhir ketika masa objek penutupan berakhir.
Mengingat itu, mari kita lihat kasus 3:
Kasus 3: Penangkapan init umum
auto lambda = [x = 33]() { std::cout << x << std::endl; };
Seperti yang dinyatakan, bayangkan ini sebagai variabel yang dibuat oleh auto x = 33;
dan ditangkap secara eksplisit oleh salinan. Variabel ini hanya "terlihat" di dalam tubuh lambda. Seperti disebutkan [expr.prim.lambda.capture]/15
sebelumnya, inisialisasi anggota yang sesuai dari tipe penutupan ( __x
untuk anak cucu) adalah oleh penginisialisasi yang diberikan pada evaluasi ekspresi lambda.
Untuk menghindari keraguan: Ini tidak berarti segala sesuatu diinisialisasi dua kali di sini. Ini auto x = 33;
adalah "seolah-olah" untuk mewarisi semantik menangkap sederhana, dan inisialisasi yang dijelaskan adalah modifikasi untuk semantik tersebut. Hanya satu inisialisasi terjadi.
Ini juga mencakup kasus 4:
auto l = [p = std::move(unique_ptr_var)]() {
// do something with unique_ptr_var
};
Anggota tipe penutupan diinisialisasi oleh __p = std::move(unique_ptr_var)
ketika ekspresi lambda dievaluasi (yaitu ketika l
ditugaskan untuk). Akses ke p
dalam tubuh lambda diubah menjadi akses ke __p
.
TL; DR: Hanya jumlah minimal salinan / inisialisasi / gerakan yang dilakukan (seperti yang diharapkan / diharapkan). Saya akan berasumsi bahwa lambda tidak ditentukan dalam hal transformasi sumber (tidak seperti gula sintaksis lainnya) persis karena mengungkapkan hal-hal dalam hal konstruktor akan memerlukan operasi yang berlebihan.
Saya harap ini menyelesaikan ketakutan yang diungkapkan dalam pertanyaan :)