Lambda tanpa status sebagai variabel lokal statis
Saya mencoba untuk menulis kelas pembungkus template di sekitar lambda tanpa kewarganegaraan. Sesuatu seperti ini:
template <class TFuncOp>
class Adapter
{
public:
void Op()
{
TFuncOp func; // not possible before C++20
func();
}
};
Karena ini tidak mungkin sebelum lambda konstruktif default tiba dengan C ++ 20, saya menggunakan teknik ini untuk membuat kelas saya berfungsi: Memanggil lambda tanpa negara tanpa contoh (hanya tipe)
Jadi solusi akhirnya terlihat seperti ini:
template <class TFuncOp>
class Adapter
{
public:
static TFuncOp GetOpImpl( TFuncOp *pFunc = 0 )
{
static TFuncOp func = *pFunc;
return func;
}
void Op()
{
GetOpImpl()();
}
};
template <class TFuncOp>
Adapter<TFuncOp> MakeAdapter(TFuncOp func )
{
// Removing the line below has no effect.
//Adapter<TFuncOp>::GetOpImpl( &func );
return Adapter<TFuncOp>();
}
int main()
{
auto adapter = MakeAdapter( [] { printf("Hello World !\n"); } );
adapter.Op();
return 0;
}
Kode ini berfungsi pada semua kompiler utama (clang, gcc, msvc). Tetapi dengan satu penemuan yang mengejutkan. Inisialisasi (atau ketiadaan) dari instance lokal statis lambda di GetOpImpl () tidak berpengaruh. Ini bekerja dengan baik.
Adakah yang bisa menjelaskan cara kerjanya? Apakah saya memanggil UB jika saya menggunakan instance lokal statis lambda tanpa memulainya?
Jawaban
Bagaimanapun, mengakses nullptrbukanlah ide yang bagus karena itu adalah UB.
Tetapi kita dapat melihat bahwa implementasi tipikal menghasilkan kode yang berfungsi dengan mudah. Saya mencoba menjelaskan mengapa:
Pertama, ini tidak ada hubungannya dengan lambda. Ini hanya tidak perlu menggunakan konstruktor salinan pada kelas yang tidak memiliki data. Karena Anda tidak memiliki data, kode yang dihasilkan tidak akan mengakses objek yang diteruskan. Dalam kasus Anda, Anda "menyalin" objek yang TFuncOp *pFunc = 0ditunjuk penunjuk , yang merupakan nullptr yang akan macet jika objek harus diakses. Karena tidak ada data untuk diakses, implementasi tipikal tidak akan menghasilkan kode apa pun yang akan mengakses nullptr sama sekali. Tapi tetap UB.
Hal yang sama bekerja dengan semua jenis lainnya dengan cara yang sama dan tidak ada yang istimewa dengan lambda!
struct Empty
{
void Do() { std::cout << "This works the same way" << std::endl; }
// int i; // << if you add some data, you get a seg fault
};
int main()
{
Empty* ptr = nullptr;
Empty empty = *ptr; // get seg fault here, because default copy constructor access the nullptr, but typically only if copy ctor needs to access!
empty.Do();
}
Dan lambda yang tidak memiliki data yang diambil, adalah struktur kosong dengan file operator()().
Itu semua adalah jawaban mengapa tampaknya berhasil.