Fungsi mengembalikan ekspresi lambda

90

Saya ingin tahu apakah mungkin untuk menulis fungsi yang mengembalikan fungsi lambda di C ++ 11. Tentu satu masalah adalah bagaimana mendeklarasikan fungsi tersebut. Setiap lambda memiliki tipe, tetapi tipe itu tidak dapat diekspresikan dalam C ++. Saya tidak berpikir ini akan berhasil:

auto retFun() -> decltype ([](int x) -> int)
{
    return [](int x) { return x; }
}

Juga bukan ini:

int(int) retFun();

Saya tidak mengetahui adanya konversi otomatis dari lambda ke, katakanlah, pointer ke fungsi, atau semacamnya. Apakah satu-satunya solusi membuat objek fungsi dan mengembalikannya?

Bartosz Milewski
sumber
1
Untuk menambahkan apa yang sudah dikatakan, fungsi lambda stateless dapat diubah menjadi pointer fungsi.
snk_kid
3
IMO opsi pertama Anda tidak akan berfungsi karena lambda di dalam decltypetidak sama dengan di badan fungsi dan oleh karena itu memiliki tipe yang berbeda (bahkan jika Anda menyertakan pernyataan return)
Motti
1
Ngomong-ngomong, jika lambda memiliki klausa capture kosong, itu bisa secara implisit diubah menjadi pointer untuk berfungsi.
GManNickG
@GMan: Kecuali Anda menggunakan Visual C ++ 2010 atau versi g ++ yang dirilis lebih dari setahun yang lalu (atau sekitar itu). Konversi implisit captureless-lambda menjadi penunjuk fungsi tidak ditambahkan hingga Maret 2010 di N3092.
James McNellis
4
Ekspresi lambda secara umum tidak dapat muncul dalam operan yang tidak dievaluasi. Jadi decltype([](){})atau sizeof([]() {})tidak berbentuk tidak peduli di mana Anda menulisnya.
Johannes Schaub - litb

Jawaban:

100

Anda tidak memerlukan objek fungsi buatan tangan, cukup gunakan std::function, yang fungsi lambda dapat dikonversi:

Contoh ini mengembalikan fungsi identitas integer:

std::function<int (int)> retFun() {
    return [](int x) { return x; };
}
Sean
sumber
6
Duh! Saya suka StackOverflow. Butuh waktu lebih lama bagi saya untuk meneliti topik tersebut lalu mendapatkan jawabannya di StackOverflow. Terima kasih Sean!
Bartosz Milewski
6
Itu akan menyebabkan alokasi memori meskipun dalam konstruktor std::function.
Maxim Egorushkin
2
@Maxim Yegorushkin std :: function telah memindahkan semantik plus dapat menggunakan pengalokasi khusus dan draf kerja C ++ 0x memiliki catatan berikut: "[Catatan: implementasi didorong untuk menghindari penggunaan memori yang dialokasikan secara dinamis untuk objek kecil yang dapat dipanggil, misalnya , di mana target f adalah sebuah objek yang hanya menyimpan sebuah pointer atau referensi ke sebuah objek dan sebuah pointer fungsi anggota. —end note] "jadi pada dasarnya Anda tidak dapat membuat banyak asumsi tentang strategi alokasi apa yang digunakan oleh implementasi tertentu tetapi Anda harus dapat untuk tetap menggunakan pengalokasi (gabungan) Anda sendiri.
snk_kid
1
@Maxim: Jawaban saya adalah untuk pertanyaan "Apakah satu-satunya solusi membuat objek fungsi dan mengembalikannya?"
Sean
2
Perlu diingat bahwa std::functionmenggunakan penghapusan tipe, yang berarti biaya untuk membuat panggilan fungsi virtual saat memanggil std::function. Sesuatu yang harus diperhatikan jika fungsi yang dikembalikan akan digunakan dalam loop dalam yang ketat atau konteks lain di mana sedikit masalah inefisiensi.
Anthony Hall
29

Untuk contoh sederhana ini, Anda tidak perlu std::function.

Dari standar §5.1.2 / 6:

Tipe closure untuk ekspresi lambda tanpa lambda-capture memiliki fungsi konversi const non-virtual publik non-eksplisit menjadi pointer ke fungsi yang memiliki parameter yang sama dan tipe kembalian sebagai operator panggilan fungsi tipe closure. Nilai yang dikembalikan oleh fungsi konversi ini harus menjadi alamat fungsi yang, ketika dipanggil, memiliki efek yang sama seperti memanggil operator panggilan fungsi tipe penutupan.

Karena fungsi Anda tidak memiliki tangkapan, itu berarti lambda dapat diubah menjadi penunjuk ke fungsi tipe int (*)(int):

typedef int (*identity_t)(int); // works with gcc
identity_t retFun() { 
  return [](int x) { return x; };
}

Itu pemahaman saya, koreksi saya jika saya salah.

pengguna534498
sumber
1
Kedengarannya benar. Sayangnya, ini tidak bekerja dengan kompiler saat ini yang saya gunakan: VS 2010. std :: function conversion kebetulan bekerja.
Bartosz Milewski
3
Ya, kata-kata terakhir dari aturan ini datang terlambat untuk VC2010.
Ben Voigt
Saya telah menambahkan contoh kode. Berikut program lengkapnya .
jfs
@ JFSebastian - Berapa umur lambda dalam contoh ini? Apakah itu cukup lama untuk hidup lebih lama dari hasil konversi ke penunjuk fungsi?
Flexo
1
@JFSebastian - Sepertinya saya tidak dapat menemukan jawaban untuk itu jadi saya telah menanyakannya sebagai pertanyaan: stackoverflow.com/questions/8026170/…
Flexo
22

Anda bisa mengembalikan fungsi lambda dari fungsi lambda lainnya, karena Anda tidak boleh secara eksplisit menentukan jenis kembalian fungsi lambda. Tulis saja sesuatu seperti itu dalam lingkup global:

 auto retFun = []() {
     return [](int x) {return x;};
 };
dzhioev
sumber
2
Itu hanya benar ketika lambda luar hanya terdiri dari pernyataan return. Jika tidak, Anda harus menentukan jenis pengembalian.
Bartosz Milewski
3
Ini adalah jawaban terbaik karena tidak memerlukan polimorfisme runtime dari std :: function dan memungkinkan lambda memiliki daftar pengambilan yang tidak kosong, namun saya akan menggunakan const auto fun = ...
robson3.14
2
@Bartoszilewski tidak benar sejak C ++ 14.
dzhioev
20

Meskipun pertanyaannya secara khusus menanyakan tentang C ++ 11, demi orang lain yang tersandung pada hal ini dan memiliki akses ke kompiler C ++ 14, C ++ 14 sekarang memungkinkan tipe pengembalian yang disimpulkan untuk fungsi biasa. Jadi contoh dalam pertanyaan dapat disesuaikan agar berfungsi seperti yang diinginkan hanya dengan menghapus -> decltypeklausa ... setelah daftar parameter fungsi:

auto retFun()
{
    return [](int x) { return x; }
}

Namun, perhatikan bahwa ini tidak akan berfungsi jika lebih dari satu return <lambda>;muncul di fungsi. Ini karena batasan pada pengurangan tipe pengembalian adalah bahwa semua pernyataan pengembalian harus mengembalikan ekspresi dari tipe yang sama, tetapi setiap objek lambda diberikan tipe uniknya sendiri oleh kompilator, sehingga return <lambda>;ekspresi masing-masing akan memiliki tipe yang berbeda.

Anthony Hall
sumber
4
Mengapa menyebutkan tipe deduksi c ++ 14 tetapi menghilangkan lambda polimorfik? auto retFun() { return [](auto const& x) { return x; }; }
lihat
1

Anda harus menulis seperti ini:

auto returnFunction = [](int x){
    return [&x](){
        return x;
    }();
};

untuk mendapatkan hasil Anda sebagai fungsi, dan menggunakannya seperti:

int val = returnFunction(someNumber);
Terens Tare
sumber
0

Jika Anda tidak memiliki c ++ 11 dan menjalankan kode c ++ pada pengontrol mikro misalnya. Anda dapat mengembalikan penunjuk kosong dan kemudian melakukan cast.

void* functionThatReturnsLambda()
{
    void(*someMethod)();

    // your lambda
    someMethod = []() {

        // code of lambda

    };

    return someMethod;
}


int main(int argc, char* argv[])
{

    void* myLambdaRaw = functionThatReturnsLambda();

    // cast it
    auto myLambda = (void(*)())myLambdaRaw;

    // execute lambda
    myLambda();
}
Tono Nam
sumber