Nilai penutupan Lambda dapat dilewatkan sebagai parameter referensi nilai ulang

18

Saya menemukan bahwa lvaluepenutupan lambda selalu dapat dilewati sebagai rvalueparameter fungsi.

Lihat demonstrasi sederhana berikut.

#include <iostream>
#include <functional>

using namespace std;

void foo(std::function<void()>&& t)
{
}

int main()
{
    // Case 1: passing a `lvalue` closure
    auto fn1 = []{};
    foo(fn1);                          // works

    // Case 2: passing a `lvalue` function object
    std::function<void()> fn2 = []{};
    foo(fn2);                          // compile error

    return 0;
}

Kasus 2 adalah perilaku standar (saya hanya menggunakan std::functionuntuk tujuan demonstrasi, tetapi jenis lainnya akan berperilaku sama).

Bagaimana dan mengapa kasus 1 berfungsi? Bagaimana keadaan fn1penutupan setelah fungsi kembali?

Sumudu
sumber
5
Saya kira itu karena fn1secara implisit dikonversi ke std::functiondalam foo(fn1). Fungsi sementara itu adalah nilai.
eike
@RichardCritten Saya benar-benar tidak yakin, jadi saya tidak memposting jawaban. Saya pikir sekarang tidak perlu yang lain.
eike
1
@ ee np Saya sering merasakan hal yang sama, dan ya banyak jawaban.
Richard Critten
2
@Sumudu Orang yang mengajukan pertanyaan itu menyesatkan Anda karena mereka tidak tahu apa yang mereka coba tanyakan. Apa yang ingin mereka tanyakan adalah: "Mengapa tidak bisa membuat templat argumen yang std::functiondiambil dari lambda". Program Anda tidak berupaya menyimpulkan argumen templat std::function, jadi tidak ada masalah dengan konversi implisit.
eerorika
1
Judul pertanyaan yang Anda tautkan sedikit menyesatkan. std::functionmemiliki konstruktor non-eksplisit yang menerima penutupan lambda, sehingga ada konversi implisit. Tetapi dalam keadaan pertanyaan terkait, instantiasi template std::functiontidak dapat disimpulkan dari jenis lambda. (Misalnya std::function<void()>dapat dibangun dari [](){return 5;}meskipun memiliki jenis kembali tidak batal.
eike

Jawaban:

8

Bagaimana dan mengapa kasus 1 berfungsi?

Meminta foomembutuhkan instance std::function<void()>yang mengikat referensi nilai . std::function<void()>dapat dibangun dari objek yang dapat dipanggil yang kompatibel dengan void()tanda tangan.

Pertama, std::function<void()>objek sementara dibangun dari []{}. Konstruktor yang digunakan adalah # 5 di sini , yang menyalin penutupan ke std::functioninstance:

template< class F >
function( F f );

Menginisialisasi target dengan std::move(f). Jika fnull pointer berfungsi atau null pointer ke anggota, *thisakan kosong setelah panggilan.

Kemudian, functioninstance sementara terikat pada referensi nilai.


Apa status penutupan fn1 setelah fungsi kembali?

Sama seperti sebelumnya, karena disalin ke std::functioninstance. Penutupan asli tidak terpengaruh.

Vittorio Romeo
sumber
8

Lambda bukan a std::function. Referensi tidak mengikat secara langsung .

Kasus 1 berfungsi karena lambdas dapat dikonversi menjadi std::functions. Ini berarti bahwa suatu sementara std::functiondiwujudkan dengan menyalin fn1 . Kata sementara dapat diikat ke referensi nilai, dan argumen cocok dengan parameter.

Dan penyalinan juga mengapa fn1sama sekali tidak terpengaruh oleh apa pun yang terjadi di foo.

StoryTeller - Unslander Monica
sumber
5

Apa status penutupan fn1 setelah fungsi kembali?

fn1 tidak memiliki kewarganegaraan, karena tidak menangkap apa pun.

Bagaimana dan mengapa kasus 1 berfungsi?

Ini berfungsi karena argumennya bertipe berbeda dari jenis yang dirujuk. Karena memiliki tipe yang berbeda, konversi implisit dipertimbangkan. Karena lambda adalah Callable untuk argumen ini std::function, maka secara implisit dapat dikonversi untuk itu melalui template yang mengkonversi konstruktor std::function. Hasil konversi adalah nilai awal, dan dengan demikian dapat diikat dengan referensi nilai.

eerorika
sumber