Mengapa saya tidak dapat membuat vektor lambda (dengan tipe yang sama) di C ++ 11?

90

Saya mencoba membuat vektor lambda, tetapi gagal:

auto ignore = [&]() { return 10; };  //1
std::vector<decltype(ignore)> v;     //2
v.push_back([&]() { return 100; });  //3

Sampai baris # 2, kompilasi baik-baik saja . Tetapi baris # 3 memberikan kesalahan kompilasi :

kesalahan: tidak ada fungsi yang cocok untuk panggilan ke 'std :: vector <main () :: <lambda () >> :: push_back (main () :: <lambda ()>)'

Saya tidak ingin vektor penunjuk fungsi atau vektor objek fungsi. Namun, vektor objek fungsi yang merangkum ekspresi lambda nyata , akan bekerja untuk saya. Apakah ini mungkin?

Nawaz
sumber
23
"Saya tidak ingin vektor penunjuk fungsi atau vektor objek fungsi." Tapi itulah yang kamu minta. Lambda adalah objek fungsi.
Nicol Bolas

Jawaban:

137

Setiap lambda memiliki jenis yang berbeda — meskipun memiliki tanda tangan yang sama. Anda harus menggunakan wadah enkapsulasi run-time seperti std::functionjika Anda ingin melakukan sesuatu seperti itu.

misalnya:

std::vector<std::function<int()>> functors;
functors.push_back([&] { return 100; });
functors.push_back([&] { return  10; });
Anak anjing
sumber
52
Mengelola tim pengembang beranggotakan seratus orang terdengar lebih seperti mimpi buruk bagi saya :)
Jeremy Friesner
10
Selain itu, jangan lupa bahwa lambda yang tidak dapat ditangkap ([] -style) dapat diturunkan menjadi pointer fungsi. Jadi dia bisa menyimpan berbagai fungsi pointer dari tipe yang sama. Perhatikan bahwa VC10 belum mengimplementasikannya.
Nicol Bolas
Omong-omong, bukankah seharusnya capture-less digunakan dalam contoh tersebut? Atau apakah itu perlu? - Ngomong-ngomong, lambda yang tidak dapat ditangkap ke penunjuk fungsi tampaknya didukung di VC11. Tidak mengujinya.
Klaim
2
Apakah mungkin untuk membuat fungsi penyimpanan vektor dari tipe yang berbeda? yaitu, alih-alih membatasi std::function<int(), dapatkah saya menggunakan prototipe fungsi yang berbeda?
manatttta
2
@manatttta Apa gunanya? Wadah ada untuk menyimpan objek dengan tipe yang sama, untuk mengatur dan memanipulasinya bersama. Anda mungkin juga bertanya 'dapatkah saya membuat vectorpenyimpanan keduanya std::functiondan std::string?' Dan jawabannya sama: Tidak, karena itu bukan tujuan penggunaan. Anda dapat menggunakan kelas gaya 'varian' untuk melakukan penghapusan jenis yang cukup untuk meletakkan hal-hal yang berbeda dalam wadah, sambil menyertakan metode bagi pengguna untuk menentukan jenis 'nyata' dan karenanya memilih apa yang harus dilakukan (misalnya bagaimana memanggil) setiap elemen ... tetapi sekali lagi, mengapa terlalu jauh? Apakah ada alasan yang nyata?
underscore_d
41

Semua ekspresi lambda memiliki jenis yang berbeda, meskipun keduanya identik karakter demi karakter . Anda mendorong lambda dari tipe yang berbeda (karena itu ekspresi lain) ke dalam vektor, dan itu jelas tidak akan berhasil.

Salah satu solusinya adalah membuat vektor std::function<int()>sebagai gantinya.

auto ignore = [&]() { return 10; };
std::vector<std::function<int()>> v;
v.push_back(ignore);
v.push_back([&]() { return 100; });

Di catatan lain, sebaiknya gunakan [&]saat Anda tidak sedang mengambil gambar apa pun.

R. Martinho Fernandes
sumber
14
Tidak perlu ()lambda yang tidak membutuhkan argumen.
Anak anjing
18

Sementara apa yang dikatakan orang lain relevan, masih mungkin untuk mendeklarasikan dan menggunakan vektor lambda, meskipun itu tidak terlalu berguna:

auto lambda = [] { return 10; };
std::vector<decltype(lambda)> vec;
vec.push_back(lambda);

Jadi, Anda dapat menyimpan sejumlah lambda di sana, selama itu adalah salinan / pemindahan lambda!

Luc Danton
sumber
Yang sebenarnya bisa berguna jika pushback terjadi dalam loop dengan parameter berbeda. Agaknya untuk tujuan evaluasi malas.
MaHuJa
7
Tidak, Anda tidak meletakkan parameter di vektor, cukup objek fungsi .. Jadi itu akan menjadi vektor dengan semua salinan lambda yang sama
hariseldon78
16

Jika lambda Anda tidak memiliki kewarganegaraan, misalnya [](...){...}, C ++ 11 memungkinkannya untuk diturunkan menjadi penunjuk fungsi. Secara teori, kompiler yang memenuhi C ++ 11 akan dapat mengkompilasi ini:

auto ignore = []() { return 10; };  //1 note misssing & in []!
std::vector<int (*)()> v;     //2
v.push_back([]() { return 100; });  //3
MSN
sumber
4
Sebagai catatan auto ignore = *[] { return 10; };akan membuat ignoresebuah int(*)().
Luc Danton
1
@ Luc, oh itu menjijikkan! Kapan mereka menambahkan itu?
MSN
3
Nah, karena fungsi konversi yang memungkinkan untuk mengambil penunjuk fungsi diamanatkan untuk tidak explicit, dereferensi ekspresi lambda adalah valid dan dereferensi penunjuk yang dihasilkan dari konversi. Kemudian menggunakan autodecays referensi itu kembali menjadi sebuah pointer. (Menggunakan auto&atau auto&&akan menyimpan referensi.)
Luc Danton
Ah ... Membedakan penunjuk yang dihasilkan. Itu masuk akal. Apakah hilang itu ()disengaja atau tidak disengaja?
MSN
Disengaja, ekspresi lambda setara (tetapi dua karakter lebih pendek).
Luc Danton
6

Anda dapat menggunakan fungsi penghasil lambda (diperbarui dengan perbaikan yang disarankan oleh Nawaz):

#include <vector>
#include <iostream>

int main() {
    auto lambda_gen = [] (int i) {return [i](int x){ return i*x;};} ;

    using my_lambda = decltype(lambda_gen(1));

    std::vector<my_lambda> vec;

    for(int i = 0; i < 10; i++) vec.push_back(lambda_gen(i));

    int i = 0;

    for (auto& lambda : vec){
        std::cout << lambda(i) << std::endl;
        i++;
    }
}

Tapi saya pikir Anda pada dasarnya membuat kelas Anda sendiri pada saat ini. Sebaliknya jika lambda memiliki caputres / args yang sangat berbeda, dll. Anda mungkin harus menggunakan tupel.

kuno
sumber
Ide bagus untuk membungkusnya dalam fungsi lambda_genyang pada gilirannya bisa menjadi lambda itu sendiri. Namun, auto a = lambda_gen(1);lakukan panggilan yang tidak perlu, yang dapat dihindari jika kita menulis ini decltype(lambda_gen(1)).
Nawaz
Bukankah itu masih membuat panggilan ekstra? Juga poin kecil lainnya adalah bahwa pertanyaan menyatakan C ++ 11 sehingga seseorang perlu menambahkan tipe kembali ke fungsi yang saya pikir.
kuno
Tidak. Apa pun di dalam decltype tidak dievaluasi , jadi panggilan tidak benar-benar dibuat. Kasus yang sama juga terjadi sizeof. Selain itu, kode ini tidak akan berfungsi di C ++ 11 bahkan jika Anda menambahkan tipe pengembalian trailing !!
Nawaz
4

Setiap lambda memiliki tipe yang berbeda. Anda harus menggunakan std::tuplebukan std::vector.

Paul Fultz II
sumber