Saya meminta trik template untuk mendeteksi apakah kelas memiliki fungsi anggota tertentu dari tanda tangan yang diberikan.
Masalahnya mirip dengan yang dikutip di sini http://www.gotw.ca/gotw/071.htm tetapi tidak sama: dalam item buku Sutter ia menjawab pertanyaan bahwa kelas C HARUS MENYEDIAKAN fungsi anggota dengan tanda tangan tertentu, jika tidak program tidak dapat dikompilasi. Dalam masalah saya, saya perlu melakukan sesuatu jika kelas memiliki fungsi itu, yang lain lakukan "sesuatu yang lain".
Masalah serupa juga dihadapi oleh boost :: serialisasi tetapi saya tidak suka solusi yang mereka adopsi: fungsi templat yang memanggil fungsi bebas secara default (yang harus Anda tetapkan) dengan tanda tangan tertentu kecuali jika Anda menetapkan fungsi anggota tertentu ( dalam kasus mereka "membuat cerita bersambung" yang mengambil 2 parameter dari jenis tertentu) dengan tanda tangan tertentu, jika tidak maka kesalahan kompilasi akan terjadi. Yaitu untuk mengimplementasikan serialisasi baik intrusif maupun non-intrusif.
Saya tidak suka solusi itu karena dua alasan:
- Agar tidak mengganggu, Anda harus mengganti fungsi "serialisasi" global yang ada di boost :: serialisasi namespace, sehingga Anda MEMILIKI DALAM KODE KLIEN ANDA untuk membuka boost namespace dan serialisasi namespace!
- Tumpukan untuk menyelesaikan kekacauan itu adalah 10 hingga 12 pemanggilan fungsi.
Saya perlu mendefinisikan perilaku khusus untuk kelas yang tidak memiliki fungsi anggota, dan entitas saya berada di dalam ruang nama yang berbeda (dan saya tidak ingin mengganti fungsi global yang didefinisikan dalam satu namespace sementara saya di yang lain)
Bisakah Anda memberi saya petunjuk untuk memecahkan teka-teki ini?
Jawaban:
Saya tidak yakin apakah saya mengerti Anda dengan benar, tetapi Anda dapat mengeksploitasi SFINAE untuk mendeteksi keberadaan fungsi pada waktu kompilasi. Contoh dari kode saya (menguji apakah kelas memiliki fungsi anggota size_t used_memory () const).
sumber
size_t(std::vector::*p)() = &std::vector::size;
,.Berikut ini adalah implementasi yang mungkin mengandalkan fitur C ++ 11. Itu benar mendeteksi fungsi bahkan jika itu diwarisi (tidak seperti solusi dalam jawaban yang diterima, seperti Mike Kinghan mengamati dalam jawabannya ).
Fungsi tes potongan ini disebut
serialize
:Pemakaian:
sumber
serialize
itu sendiri menerima templat. Apakah ada cara untuk mengujiserialize
keberadaan tanpa mengetik jenis yang tepat?Jawaban yang diterima untuk pertanyaan introspeksi fungsi-anggota ini, meskipun populer, memiliki hambatan yang dapat diamati dalam program berikut:
Dibangun dengan GCC 4.6.3, program keluaran
110
- memberitahu kita bahwaT = std::shared_ptr<int>
tidak tidak memberikanint & T::operator*() const
.Jika Anda belum bijaksana dengan gotcha ini, maka melihat definisi
std::shared_ptr<T>
di header<memory>
akan menjelaskan. Dalam implementasi itu,std::shared_ptr<T>
diturunkan dari kelas dasar dari mana ia mewarisioperator*() const
. Jadi contoh templateSFINAE<U, &U::operator*>
yang merupakan "menemukan" operator untukU = std::shared_ptr<T>
tidak akan terjadi, karenastd::shared_ptr<T>
tidak adaoperator*()
memiliki haknya sendiri dan instantiasi template tidak "melakukan pewarisan".Halangan ini tidak mempengaruhi pendekatan SFINAE yang terkenal, menggunakan "Trik sizeof ()", untuk mendeteksi hanya apakah
T
memiliki beberapa fungsi anggotamf
(lihat misalnya jawaban dan komentar ini). Tetapi membangun yangT::mf
ada sering (biasanya?) Tidak cukup baik: Anda mungkin juga perlu memastikan bahwa itu memiliki tanda tangan yang diinginkan. Di situlah skor teknik digambarkan. Varian terarah dari tanda tangan yang diinginkan tertulis dalam parameter tipe templat yang harus dipenuhi&T::mf
agar probe SFINAE berhasil. Namun teknik cetakan template ini memberikan jawaban yang salah ketikaT::mf
diwariskan.Teknik SFINAE yang aman untuk introspeksi kompilasi dari
T::mf
harus menghindari penggunaan di&T::mf
dalam argumen templat untuk membuat instantiate jenis di mana resolusi templat fungsi SFINAE bergantung. Sebagai gantinya, resolusi fungsi templat SFINAE hanya dapat bergantung pada deklarasi tipe yang tepat terkait yang digunakan sebagai tipe argumen dari fungsi probe SFINAE yang kelebihan beban.Sebagai jawaban atas pertanyaan yang mematuhi batasan ini, saya akan mengilustrasikan untuk pendeteksian waktu
E T::operator*() const
, untuk arbitrerT
danE
. Pola yang sama akan berlaku mutatis mutandis untuk menyelidiki tanda tangan metode anggota lainnya.Dalam solusi ini, fungsi probe SFINAE yang kelebihan beban
test()
"dipanggil secara rekursif". (Tentu saja itu tidak benar-benar dipanggil sama sekali; itu hanya memiliki jenis pengembalian pemanggilan hipotetis diselesaikan oleh kompiler.)Kami perlu menyelidiki setidaknya satu dan paling banyak dua poin informasi:
T::operator*()
ada sama sekali? Jika tidak, kita sudah selesai.T::operator*()
ada, apakah tanda tangannyaE T::operator*() const
?Kami mendapatkan jawaban dengan mengevaluasi tipe kembali dari satu panggilan ke
test(0,0)
. Itu dilakukan oleh:Panggilan ini mungkin diselesaikan dengan
/* SFINAE operator-exists :) */
kelebihantest()
, atau mungkin memutuskan untuk/* SFINAE game over :( */
kelebihan. Itu tidak dapat mengatasi/* SFINAE operator-has-correct-sig :) */
kelebihan, karena yang satu hanya mengharapkan satu argumen dan kami melewati dua.Mengapa kita melewati dua? Cukup dengan memaksa resolusi untuk dikecualikan
/* SFINAE operator-has-correct-sig :) */
. Argumen kedua tidak memiliki arti lain.Panggilan ke ini
test(0,0)
akan diselesaikan untuk/* SFINAE operator-exists :) */
berjaga-jaga jika argumen pertama 0 mengesahkan jenis parameter pertama dari kelebihan itu, yaitudecltype(&A::operator*)
, denganA = T
. 0 akan memenuhi jenis itu untuk berjaga-jaga jikaT::operator*
ada.Anggap saja kompiler mengatakan Ya untuk itu. Maka itu akan dengan
/* SFINAE operator-exists :) */
dan perlu menentukan jenis kembali dari panggilan fungsi, yang dalam hal ini adalahdecltype(test(&A::operator*))
- jenis kembali dari panggilan lain untuktest()
.Kali ini, kami hanya menyampaikan satu argumen
&A::operator*
, yang sekarang kami tahu ada, atau kami tidak akan berada di sini. Panggilan ketest(&A::operator*)
mungkin diselesaikan baik ke/* SFINAE operator-has-correct-sig :) */
atau lagi ke mungkin diselesaikan/* SFINAE game over :( */
. Panggilan akan cocok untuk/* SFINAE operator-has-correct-sig :) */
berjaga-jaga jika&A::operator*
memenuhi jenis parameter tunggal dari kelebihan itu, yaituE (A::*)() const
, denganA = T
.Kompiler akan mengatakan Ya di sini jika
T::operator*
memiliki tanda tangan yang diinginkan, dan sekali lagi harus mengevaluasi jenis pengembalian kelebihan. Tidak ada lagi "rekursi" sekarang: itustd::true_type
.Jika kompiler tidak memilih
/* SFINAE operator-exists :) */
untuk panggilantest(0,0)
atau tidak memilih/* SFINAE operator-has-correct-sig :) */
untuk panggilantest(&A::operator*)
, maka dalam kedua kasus itu berjalan dengan/* SFINAE game over :( */
dan jenis pengembalian akhir adalahstd::false_type
.Berikut adalah program pengujian yang menunjukkan templat yang menghasilkan jawaban yang diharapkan dalam beragam sampel kasus (GCC 4.6.3 lagi).
Apakah ada kekurangan baru dalam ide ini? Bisakah itu dibuat lebih umum tanpa sekali lagi jatuh dari halangan yang dihindarinya?
sumber
Berikut beberapa cuplikan penggunaan: * Nyali untuk semua ini lebih jauh ke bawah
Periksa anggota
x
di kelas yang diberikan. Bisa berupa var, func, class, union, atau enum:Periksa fungsi anggota
void x()
:Periksa variabel anggota
x
:Periksa kelas anggota
x
:Periksa serikat anggota
x
:Periksa enum anggota
x
:Periksa fungsi anggota apa pun
x
tanpa tanda tangan:ATAU
Detail dan inti:
Makro (El Diablo!):
CREATE_MEMBER_CHECK:
CREATE_MEMBER_VAR_CHECK:
CREATE_MEMBER_FUNC_SIG_CHECK:
CREATE_MEMBER_CLASS_CHECK:
CREATE_MEMBER_UNION_CHECK:
CREATE_MEMBER_ENUM_CHECK:
CREATE_MEMBER_FUNC_CHECK:
CREATE_MEMBER_CHECKS:
sumber
Ini harus cukup, jika Anda tahu nama fungsi anggota yang Anda harapkan. (Dalam hal ini, fungsi bla gagal untuk instantiate jika tidak ada fungsi anggota (menulis yang berfungsi tetap sulit karena ada kekurangan spesialisasi fungsi parsial. Anda mungkin perlu menggunakan templat kelas) Juga, memungkinkan struct (yang mirip dengan enable_if) juga dapat digunakan pada jenis fungsi yang Anda inginkan sebagai anggota.
sumber
Ini jawaban sederhana dari jawaban Mike Kinghan. Ini akan mendeteksi metode yang diwarisi. Itu juga akan memeriksa tanda tangan yang tepat (tidak seperti pendekatan jrok yang memungkinkan konversi argumen).
Contoh runnable
sumber
using
untuk membawa kelebihan dari kelas dasar. Ini bekerja untuk saya di MSVC 2015 dan dengan Dentang-CL. Namun tidak bekerja dengan MSVC 2012.Anda dapat menggunakan std :: is_member_function_pointer
sumber
&A::foo
akan ada kesalahan kompilasi jika tidak adafoo
sama sekali di dalamnyaA
? Saya membaca pertanyaan asli yang seharusnya bekerja dengan kelas input apa pun, bukan hanya yang memiliki nama anggotafoo
.Datang dengan masalah yang sama sendiri, dan menemukan solusi yang diusulkan di sini sangat menarik ... tetapi memiliki persyaratan untuk solusi yang:
Temukan utas lain yang mengusulkan sesuatu seperti ini, berdasarkan pada diskusi BOOST . Berikut adalah generalisasi dari solusi yang diusulkan sebagai dua deklarasi makro untuk kelas ciri, mengikuti model kelas boost :: has_ * .
Makro ini diperluas ke kelas ciri dengan prototipe berikut:
Jadi, apa penggunaan khas yang bisa dilakukan dari ini?
sumber
Untuk mencapai ini kita harus menggunakan:
type_traits
header, kami ingin mengembalikan atrue_type
ataufalse_type
dari kelebihan kamitrue_type
kelebihan ekspektasiint
danfalse_type
kelebihan ekspektasi Parameter Variadik untuk dieksploitasi: "Prioritas terendah konversi elipsis dalam resolusi kelebihan beban"true_type
fungsi yang akan kita gunakandeclval
dandecltype
memungkinkan kita untuk mendeteksi fungsi yang tidak bergantung pada perbedaan jenis pengembalian atau kelebihan antar metodeAnda dapat melihat contoh langsung dari ini di sini . Tetapi saya juga akan menjelaskannya di bawah ini:
Saya ingin memeriksa keberadaan fungsi bernama
test
yang mengambil tipe convertibleint
, maka saya perlu mendeklarasikan dua fungsi ini:decltype(hasTest<a>(0))::value
adalahtrue
(Catatan tidak perlu membuat fungsionalitas khusus untuk menanganivoid a::test()
kelebihan beban,void a::test(int)
ini diterima)decltype(hasTest<b>(0))::value
istrue
(Karenaint
dapat dikonversi kedouble
int b::test(double)
diterima, terlepas dari jenis pengembalian)decltype(hasTest<c>(0))::value
isfalse
(c
tidak memiliki metode bernamatest
yang menerima tipe convertible dari siniint
karena ini tidak diterima)Solusi ini memiliki 2 kelemahan:
test()
metode?Jadi, penting bahwa fungsi-fungsi ini dideklarasikan dalam namespace detail, atau idealnya jika hanya digunakan dengan kelas, mereka harus dideklarasikan secara pribadi oleh kelas itu. Untuk itu saya telah menulis makro untuk membantu Anda abstrak informasi ini:
Anda bisa menggunakan ini seperti:
Selanjutnya memanggil
details::test_int<a>::value
ataudetails::test_void<a>::value
akan menghasilkantrue
ataufalse
untuk tujuan inline code atau meta-programming.sumber
Agar tidak mengganggu, Anda juga dapat memasukkan
serialize
namespace dari kelas yang diserialisasi, atau dari kelas arsip, berkat pencarian Koenig . Lihat Namespace untuk Pengubahan Fungsi Gratis untuk detail lebih lanjut. :-)Membuka setiap namespace yang diberikan untuk mengimplementasikan fungsi gratis adalah Simply Wrong. (mis. Anda tidak seharusnya membuka namespace
std
untuk diterapkanswap
untuk tipe Anda sendiri, tetapi sebaiknya gunakan pencarian Koenig.)sumber
Anda tampaknya menginginkan idiom detektor. Jawaban di atas adalah variasi untuk ini yang bekerja dengan C ++ 11 atau C ++ 14.
The
std::experimental
perpustakaan memiliki fitur yang melakukan dasarnya ini. Mengolah contoh dari atas, mungkin:Jika Anda tidak dapat menggunakan std :: eksperimental, versi yang belum sempurna dapat dibuat seperti ini:
Karena has_serialize_t benar-benar std :: true_type atau std :: false_type, ini dapat digunakan melalui salah satu idiom SFINAE yang umum:
Atau dengan menggunakan pengiriman dengan resolusi kelebihan:
sumber
Baik. Percobaan kedua. Tidak apa-apa jika Anda tidak menyukai yang ini juga, saya mencari lebih banyak ide.
Artikel Herb Sutter berbicara tentang sifat-sifat. Jadi Anda dapat memiliki kelas sifat yang instantiasi defaultnya memiliki perilaku mundur, dan untuk setiap kelas di mana fungsi anggota Anda ada, maka kelas sifat khusus untuk menjalankan fungsi anggota. Saya percaya artikel Herb menyebutkan teknik untuk melakukan ini sehingga tidak melibatkan banyak menyalin dan menempel.
Seperti yang saya katakan, mungkin Anda tidak ingin pekerjaan tambahan yang terlibat dengan "penandaan" kelas yang mengimplementasikan anggota itu. Dalam hal ini, saya sedang mencari solusi ketiga ....
sumber
Tanpa dukungan C ++ 11 (
decltype
) ini mungkin berfungsi:SSCCE
Semoga ini berhasil
A
,Aa
danB
apakah klas yang dimaksud,Aa
adalah yang istimewa yang mewarisi anggota yang kami cari.Dalam
FooFinder
dalamtrue_type
danfalse_type
merupakan pengganti koresponden C ++ 11 kelas. Juga untuk memahami pemrograman meta templat, mereka mengungkapkan dasar dari trik-ukuran SFINAE.Itu
TypeSink
adalah kerangka templat yang digunakan kemudian untuk menenggelamkan hasil integral darisizeof
operator ke dalam contoh kerangka untuk membentuk suatu jenis.The
match
fungsi lain SFINAE jenis template yang dibiarkan tanpa rekan generik. Oleh karena itu hanya dapat dipakai jika jenis argumennya cocok dengan jenis yang dikhususkan untuknya.Keduanya
test
fungsi bersama dengan deklarasi enum akhirnya membentuk pola SFINAE pusat. Ada yang umum menggunakan elipsis yang mengembalikanfalse_type
dan rekan dengan argumen yang lebih spesifik untuk diutamakan.Untuk dapat instantiate
test
fungsi dengan argumen templatT
,match
fungsi harus instantiated, karena jenis kembalinya diperlukan untuk instantiateTypeSink
argumen. Peringatan adalah bahwa&U::foo
, yang dibungkus dalam argumen fungsi, tidak dirujuk dari dalam spesialisasi argumen templat, sehingga pencarian anggota yang diwariskan masih berlangsung.sumber
Jika Anda menggunakan kebodohan Facebook, makro kebodohan di luar kotak untuk membantu Anda:
Meskipun detail implementasi sama dengan jawaban sebelumnya, menggunakan perpustakaan lebih sederhana.
sumber
Saya memiliki kebutuhan yang sama dan menemukan SO ini. Ada banyak solusi menarik / kuat yang diusulkan di sini, meskipun agak lama hanya untuk kebutuhan spesifik: mendeteksi apakah kelas memiliki fungsi anggota dengan tanda tangan yang tepat. Jadi saya melakukan beberapa pembacaan / pengujian dan muncul dengan versi saya yang mungkin menarik. Ini mendeteksi:
dengan tanda tangan yang tepat. Karena saya tidak perlu menangkap tanda tangan apa pun (yang membutuhkan solusi yang lebih rumit), yang ini cocok untuk saya. Ini pada dasarnya menggunakan enable_if_t .
Output:
sumber
Membangun jrok 's jawaban , saya menghindari menggunakan kelas template yang bersarang dan / atau fungsi.
Kita dapat menggunakan makro di atas seperti di bawah ini:
Saran diterima.
sumber