Apa jenis lambda saat disimpulkan dengan "auto" di C ++ 11?

145

Saya memiliki persepsi bahwa, jenis lambda adalah penunjuk fungsi. Ketika saya melakukan tes berikut, saya menemukan itu salah ( demo ).

#define LAMBDA [] (int i) -> long { return 0; }
int main ()
{
  long (*pFptr)(int) = LAMBDA;  // ok
  auto pAuto = LAMBDA;  // ok
  assert(typeid(pFptr) == typeid(pAuto));  // assertion fails !
}

Apakah kode di atas kehilangan poin? Jika tidak, apa typeofekspresi lambda ketika disimpulkan dengan autokata kunci?

iammilind
sumber
8
"Jenis lambda adalah penunjuk fungsi" - yang tidak efisien dan melewatkan keseluruhan poin lambda.
Konrad Rudolph

Jawaban:

149

Jenis ekspresi lambda tidak ditentukan.

Tetapi mereka umumnya hanyalah gula sintaksis untuk para fungsional. Lambda diterjemahkan langsung ke dalam sebuah functor. Semua yang ada di dalam []akan diubah menjadi parameter konstruktor dan anggota objek functor, dan parameter di dalamnya ()diubah menjadi parameter untuk functor operator().

Sebuah lambda yang tidak menangkap variabel (tidak ada di dalam []) dapat diubah menjadi penunjuk fungsi (MSVC2010 tidak mendukung ini, jika itu kompiler Anda, tetapi konversi ini adalah bagian dari standar).

Tapi tipe lambda yang sebenarnya bukanlah penunjuk fungsi. Ini beberapa jenis functor yang tidak ditentukan.

jalf
sumber
1
MSVC2010 tidak mendukung konversi ke penunjuk fungsi, tetapi MSVC11 mendukung. blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx
KindDragon
19
1 untuk "gula sintaksis belaka untuk para fungsional." Banyak potensi kebingungan dapat dihindari dengan mengingat ini.
Ben
4
Functor adalah segalanya dengan operator()pada dasarnya stackoverflow.com/questions/356950/c-functors-and-their-uses
TankorSmash
111

Ini adalah struktur unik tanpa nama yang membebani operator pemanggil fungsi. Setiap contoh lambda memperkenalkan tipe baru.

Dalam kasus khusus lambda yang tidak menangkap, struktur tersebut memiliki konversi implisit ke penunjuk fungsi.

avakar
sumber
2
Jawaban bagus. Jauh lebih tepat dariku. +1 :)
jalf
4
1 untuk bagian kesatuan, pada awalnya sangat mengejutkan dan patut diperhatikan.
Matthieu M.
Bukan berarti itu penting, tetapi apakah jenisnya benar-benar tidak disebutkan namanya, atau hanya tidak diberi nama sampai waktu kompilasi? IOW, dapatkah seseorang menggunakan RTTI untuk menemukan nama yang diputuskan oleh kompiler?
Ben
3
@ Ben, ini tidak dinamai dan sejauh menyangkut bahasa C ++, tidak ada hal seperti "nama yang diputuskan oleh compiler". Hasil dari type_info::name()adalah implementasi-didefinisikan, sehingga dapat mengembalikan apapun. Dalam praktiknya, kompilator akan menamai tipe tersebut demi linker.
avakar
1
Akhir-akhir ini, ketika ditanya pertanyaan ini, saya biasanya mengatakan bahwa tipe lambda memiliki nama, kompiler mengetahuinya, hanya tidak dapat diucapkan.
Andre Kostur
25

[C++11: 5.1.2/3]: Tipe ekspresi lambda (yang juga merupakan tipe objek closure) adalah tipe kelas non-union unik dan tak bernama - disebut tipe closure - yang propertinya dijelaskan di bawah ini. Jenis kelas ini bukan agregat (8.5.1). Jenis closure dideklarasikan dalam lingkup blok terkecil, cakupan kelas, atau lingkup namespace yang berisi ekspresi lambda yang sesuai . [..]

Klausul tersebut melanjutkan ke daftar berbagai properti dari jenis ini. Berikut beberapa sorotan:

[C++11: 5.1.2/5]:Jenis penutupan untuk lambda ekspresi memiliki publik inlineOperator fungsi panggilan (13.5.4) yang parameter dan jenis kembali dijelaskan oleh lambda ekspresi ‘s parameter-deklarasi-klausul dan trailing return-jenis masing-masing. [..]

[C++11: 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.

Konsekuensi dari bagian terakhir ini adalah, jika Anda menggunakan sebuah konversi, Anda akan dapat menetapkannya LAMBDA ke pFptr.

Balapan Ringan dalam Orbit
sumber
4
#include <iostream>
#include <typeinfo>

#define LAMBDA [] (int i)->long { return 0l; }
int main ()
{
  long (*pFptr)(int) = LAMBDA;  // ok
  auto pAuto = LAMBDA;  // ok

  std::cout<<typeid( *pAuto ).name() << std::endl;
  std::cout<<typeid( *pFptr ).name() << std::endl;

  std::cout<<typeid( pAuto ).name() << std::endl;
  std::cout<<typeid( pFptr ).name() << std::endl;
}

Jenis fungsi memang sama, tetapi lambda memperkenalkan jenis baru (seperti functor).

BЈовић
sumber
Saya merekomendasikan pendekatan penguraian CXXABI jika Anda sudah mengikuti rute ini. Sebaliknya, saya biasanya memanfaatkan __PRETTY_FUNCTION__, seperti template<class T> const char* pretty(T && t) { return __PRETTY_FUNCTION__; }, dan menanggalkan ekstra jika mulai ramai. Saya lebih suka melihat langkah-langkah yang ditunjukkan dalam penggantian template. Jika Anda tidak ada __PRETTY_FUNCTION__, ada alternatif untuk MSVC, dll., Tetapi hasilnya selalu bergantung pada kompilator karena alasan yang sama CXXABI diperlukan.
John P
1

Perlu juga dicatat bahwa lambda dapat diubah menjadi penunjuk fungsi. Namun typeid <> mengembalikan objek non-trvial yang harus berbeda dari lambda ke penunjuk fungsi generik. Jadi pengujian untuk typeid <> bukanlah asumsi yang valid. Secara umum C ++ 11 tidak ingin kita khawatir tentang spesifikasi tipe, semua itu penting jika tipe tertentu dapat dikonversi ke tipe target.

Syed Raihan
sumber
Itu adil, tetapi jenis pencetakan sangat membantu untuk mendapatkan jenis yang benar, belum lagi menangkap kasus di mana jenisnya dapat dikonversi tetapi tidak memenuhi batasan lain. (Saya akan selalu mendorong ke arah batasan "reifying" jika memungkinkan, tetapi seseorang yang mencoba melakukannya memiliki lebih banyak alasan untuk menunjukkan pekerjaan mereka selama pengembangan.)
John P