Apakah mungkin untuk menulis templat yang mengubah perilaku tergantung pada apakah fungsi anggota tertentu didefinisikan pada kelas?
Berikut adalah contoh sederhana dari apa yang ingin saya tulis:
template<class T>
std::string optionalToString(T* obj)
{
if (FUNCTION_EXISTS(T->toString))
return obj->toString();
else
return "toString not defined";
}
Jadi, jika class T
telah toString()
didefinisikan, maka ia menggunakannya; kalau tidak, tidak. Bagian ajaib yang saya tidak tahu bagaimana melakukannya adalah bagian "FUNCTION_EXISTS".
Jawaban:
Ya, dengan SFINAE Anda dapat memeriksa apakah kelas yang diberikan menyediakan metode tertentu. Ini kode kerjanya:
Saya baru saja mengujinya dengan Linux dan gcc 4.1 / 4.3. Saya tidak tahu apakah ini portabel untuk platform lain yang menjalankan kompiler berbeda.
sumber
typeof
dengandecltype
saat menggunakan C ++ 0x , misalnya, melalui -std = c ++ 0x.Pertanyaan ini sudah tua, tetapi dengan C ++ 11 kami mendapatkan cara baru untuk memeriksa keberadaan fungsi (atau keberadaan anggota non-tipe, benar-benar), mengandalkan SFINAE lagi:
Sekarang ke beberapa penjelasan. Hal pertama, saya menggunakan ekspresi SFINAE untuk mengecualikan
serialize(_imp)
fungsi dari resolusi overload, jika ekspresi pertama di dalamdecltype
tidak valid (alias, fungsi tidak ada).Yang
void()
digunakan untuk membuat jenis kembalinya semua fungsi-fungsivoid
.The
0
argumen digunakan untuk lebih memilihos << obj
berlebihan jika keduanya tersedia (literal0
adalah tipeint
dan dengan demikian overload pertama adalah pertandingan yang lebih baik).Sekarang, Anda mungkin ingin suatu sifat memeriksa apakah suatu fungsi ada. Untungnya, mudah untuk menulisnya. Namun, perlu diketahui bahwa Anda harus menulis sifat sendiri untuk setiap nama fungsi berbeda yang Anda inginkan.
Contoh langsung.
Dan ke penjelasan. Pertama,
sfinae_true
adalah tipe pembantu, dan pada dasarnya sama dengan menulisdecltype(void(std::declval<T>().stream(a0)), std::true_type{})
. Keuntungannya adalah lebih pendek.Selanjutnya,
struct has_stream : decltype(...)
warisan dari salah satustd::true_type
ataustd::false_type
pada akhirnya, tergantung pada apakahdecltype
check-intest_stream
gagal atau tidak.Terakhir,
std::declval
memberi Anda "nilai" dari jenis apa pun yang Anda lulus, tanpa Anda perlu tahu bagaimana Anda bisa membangunnya. Perhatikan bahwa ini hanya mungkin di dalam konteks yang tidak dievaluasi, sepertidecltype
,sizeof
dan lainnya.Catatan yang
decltype
belum tentu diperlukan, karenasizeof
(dan semua konteks yang tidak dievaluasi) mendapatkan peningkatan itu. Hanya sajadecltype
sudah memberikan jenis dan dengan demikian hanya bersih. Ini adalahsizeof
versi dari salah satu kelebihan:The
int
danlong
parameter masih ada untuk alasan yang sama. Array pointer digunakan untuk menyediakan konteks di manasizeof
dapat digunakan.sumber
decltype
oversizeof
adalah juga sementara tidak diperkenalkan oleh aturan yang dibuat khusus untuk pemanggilan fungsi (jadi Anda tidak harus memiliki hak akses ke destructor dari tipe return dan tidak akan menyebabkan instantiasi tersirat jika tipe return adalah contoh template kelas).static_assert(has_stream<X, char>() == true, "fail X");
akan dikompilasi dan tidak menegaskan karena char dapat dikonversi ke int, jadi jika perilaku itu tidak diinginkan dan ingin semua jenis argumen cocok, saya tidak tahu bagaimana itu bisa dicapai?C ++ memungkinkan SFINAE untuk digunakan untuk ini (perhatikan bahwa dengan fitur C ++ 11 ini lebih sederhana karena mendukung SFINAE yang diperluas pada ekspresi yang hampir sewenang-wenang - di bawah ini dibuat untuk bekerja dengan kompiler C ++ 03 yang umum):
template dan makro di atas mencoba untuk instantiate templat, memberinya jenis pointer fungsi anggota, dan pointer fungsi anggota yang sebenarnya. Jika jenisnya tidak sesuai, SFINAE menyebabkan templat diabaikan. Penggunaan seperti ini:
Tetapi perhatikan bahwa Anda tidak bisa hanya memanggil
toString
fungsi itu di cabang if. karena kompiler akan memeriksa validitas di kedua cabang, itu akan gagal untuk kasus fungsi tidak ada. Salah satu caranya adalah dengan menggunakan SFINAE sekali lagi (enable_if dapat diperoleh dari boost juga):Bersenang-senang menggunakannya. Keuntungannya adalah ia juga berfungsi untuk fungsi anggota yang kelebihan beban, dan juga untuk fungsi anggota konst (ingat gunakan
std::string(T::*)() const
sebagai tipe fungsi penunjuk anggota lalu!).sumber
type_check
digunakan untuk memastikan bahwa tanda tangan setuju persis. Apakah ada cara untuk membuatnya sehingga akan cocok dengan metode apa pun yang dapat dipanggil dengan cara yang disebut metode dengan tanda tanganSign
? (Misalnya, jikaSign
=std::string(T::*)()
, memungkinkanstd::string T::toString(int default = 42, ...)
untuk mencocokkan.)T
harus bukan tipe primitif, karena deklarasi pointer-to-method-of-T tidak tunduk pada SFINAE dan akan kesalahan keluar untuk T. non-kelas T. IMO solusi termudah adalah menggabungkan denganis_class
memeriksa dari dorongan.toString
fungsi saya templated?C ++ 20 -
requires
ekspresiDengan C ++ 20 datang konsep dan berbagai macam alat seperti
requires
ekspresi yang merupakan cara bawaan untuk memeriksa keberadaan fungsi. Dengan mereka Anda dapat menulis ulangoptionalToString
fungsi Anda sebagai berikut:Pra-C ++ 20 - Toolkit deteksi
N4502 mengusulkan toolkit deteksi untuk dimasukkan ke dalam pustaka standar C ++ 17 yang akhirnya membuatnya menjadi pustaka dasar TS v2. Kemungkinan besar tidak akan pernah masuk ke dalam standar karena sudah dimasukkan oleh
requires
ekspresi sejak itu, tetapi masih memecahkan masalah dengan cara yang agak elegan. Toolkit ini memperkenalkan beberapa metafungsi, termasukstd::is_detected
yang dapat digunakan untuk dengan mudah menulis metafungsi deteksi tipe atau fungsi di atasnya. Inilah cara Anda dapat menggunakannya:Perhatikan bahwa contoh di atas tidak diuji. Toolkit deteksi belum tersedia di perpustakaan standar tetapi proposal berisi implementasi penuh yang dapat Anda salin dengan mudah jika Anda benar-benar membutuhkannya. Bermain bagus dengan fitur C ++ 17
if constexpr
:C ++ 14 - Tingkatkan .Hana
Boost.Hana tampaknya membangun contoh spesifik ini dan memberikan solusi untuk C ++ 14 dalam dokumentasinya, jadi saya akan mengutipnya secara langsung:
Tingkatkan.TTI
Toolkit lain yang agak idiomatis untuk melakukan pemeriksaan semacam itu - meskipun kurang elegan - adalah Boost.TTI , diperkenalkan dalam Boost 1.54.0. Sebagai contoh, Anda harus menggunakan makro
BOOST_TTI_HAS_MEMBER_FUNCTION
. Inilah cara Anda dapat menggunakannya:Kemudian, Anda bisa menggunakan
bool
untuk membuat cek SFINAE.Penjelasan
Makro
BOOST_TTI_HAS_MEMBER_FUNCTION
menghasilkan metafungsihas_member_function_toString
yang mengambil tipe yang diperiksa sebagai parameter templat pertama. Parameter templat kedua sesuai dengan jenis pengembalian fungsi anggota, dan parameter berikut ini sesuai dengan jenis parameter fungsi. Anggotavalue
berisitrue
jika kelasT
memiliki fungsi anggotastd::string toString()
.Atau,
has_member_function_toString
dapat mengambil pointer fungsi anggota sebagai parameter template. Oleh karena itu, dimungkinkan untuk menggantihas_member_function_toString<T, std::string>::value
denganhas_member_function_toString<std::string T::* ()>::value
.sumber
Meskipun pertanyaan ini berumur dua tahun, saya akan berani menambahkan jawaban saya. Semoga ini akan menjelaskan solusi yang sebelumnya, sangat bagus, tak terbantahkan. Saya mengambil jawaban yang sangat membantu dari Nicola Bonelli dan Johannes Schaub dan menggabungkannya menjadi solusi yang, IMHO, lebih mudah dibaca, jelas, dan tidak memerlukan
typeof
ekstensi:Saya memeriksanya dengan gcc 4.1.2. Penghargaan terutama diberikan kepada Nicola Bonelli dan Johannes Schaub, jadi beri mereka suara jika jawaban saya membantu Anda :)
sumber
toString
. Jika Anda menulis pustaka generik, yang ingin bekerja dengan kelas apa pun di luar sana (pikirkan sesuatu seperti dorongan), maka mengharuskan pengguna untuk menentukan spesialisasi tambahan dari beberapa templat yang tidak jelas mungkin tidak dapat diterima. Terkadang lebih baik menulis kode yang sangat rumit agar antarmuka publik sesederhana mungkin.Solusi sederhana untuk C ++ 11:
Pembaruan, 3 tahun kemudian: (dan ini belum diuji). Untuk menguji keberadaannya, saya pikir ini akan berhasil:
sumber
template<typename>
sebelum variadic overload: itu tidak dipertimbangkan untuk resolusi.Inilah jenis sifat yang ada untuk. Sayangnya, mereka harus didefinisikan secara manual. Dalam kasus Anda, bayangkan berikut ini:
sumber
&T::x
atau secara implisit dengan mengikatnya ke referensi).type traits
kompilasi bersyarat di C ++ 11Nah, pertanyaan ini sudah memiliki daftar jawaban yang panjang, tetapi saya ingin menekankan komentar dari Morwenn: ada proposal untuk C ++ 17 yang membuatnya sangat sederhana. Lihat N4502 untuk detailnya, tetapi sebagai contoh lengkap pertimbangkan hal berikut.
Bagian ini adalah bagian konstan, taruh di header.
kemudian ada bagian variabel, di mana Anda menentukan apa yang Anda cari (tipe, tipe anggota, fungsi, fungsi anggota dll.). Dalam hal OP:
Contoh berikut, diambil dari N4502 , menunjukkan penyelidikan yang lebih rumit:
Dibandingkan dengan implementasi lain yang dijelaskan di atas, yang ini cukup sederhana: set alat yang dikurangi (
void_t
dandetect
) cukup, tidak perlu makro berbulu. Selain itu, dilaporkan (lihat N4502 ) bahwa itu terukur lebih efisien (kompilasi waktu dan konsumsi memori kompiler) daripada pendekatan sebelumnya.Ini adalah contoh nyata . Ini berfungsi baik dengan Dentang, tetapi sayangnya, versi GCC sebelum 5.1 mengikuti interpretasi yang berbeda dari standar C ++ 11 yang menyebabkan
void_t
tidak berfungsi seperti yang diharapkan. Yakk sudah menyediakan solusi: gunakan definisi berikut darivoid_t
( void_t dalam daftar parameter berfungsi tetapi tidak sebagai tipe pengembalian ):sumber
Ini adalah solusi C ++ 11 untuk masalah umum jika "Jika saya melakukan X, apakah akan dikompilasi?"
Ciri
has_to_string
seperti ituhas_to_string<T>::value
adalahtrue
jika dan hanya jikaT
memiliki metode.toString
yang dapat dipanggil dengan 0 argumen dalam konteks ini.Selanjutnya, saya akan menggunakan pengiriman tag:
yang cenderung lebih dapat dikelola daripada ekspresi SFINAE kompleks.
Anda dapat menulis sifat-sifat ini dengan makro jika Anda menemukan diri Anda melakukannya banyak, tetapi mereka relatif sederhana (masing-masing beberapa baris) jadi mungkin tidak sepadan:
apa yang dilakukan di atas adalah membuat makro
MAKE_CODE_TRAIT
. Anda memberikan nama sifat yang Anda inginkan, dan beberapa kode yang dapat menguji jenisnyaT
. Jadi:menciptakan kelas ciri di atas.
Selain itu, teknik di atas adalah bagian dari apa yang disebut MS "ekspresi SFINAE", dan kompiler 2013 gagal cukup keras.
Perhatikan bahwa dalam C ++ 1y sintaks berikut ini dimungkinkan:
yang merupakan cabang bersyarat kompilasi inline yang menyalahgunakan banyak fitur C ++. Melakukannya mungkin tidak sepadan, karena manfaat (dari kode yang sebaris) tidak sebanding dengan biaya (di samping tidak ada yang mengerti cara kerjanya), tetapi keberadaan solusi di atas mungkin menarik.
sumber
has_to_string
.Berikut ini 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
sig_check<func_sig, &T::func_name>
ke pengecekan fungsi bebas:sig_check<func_sig, &func_name>
gagal dibangun dengan "pengidentifikasi yang tidak dideklarasikan" dengan menyebutkan nama fungsi yang ingin kami periksa? karena saya berharap SFINAE TIDAK membuat kesalahan, itu hanya untuk anggota, mengapa tidak untuk fungsi gratis?Saya menulis jawaban untuk ini di utas lain yang (tidak seperti solusi di atas) juga memeriksa fungsi anggota yang diwarisi:
SFINAE untuk memeriksa fungsi anggota yang diwarisi
Berikut adalah beberapa contoh dari solusi itu:
Contoh 1:
Kami sedang memeriksa seorang anggota dengan tanda tangan berikut:
T::const_iterator begin() const
Harap perhatikan bahwa ini bahkan memeriksa keteguhan metode ini, dan bekerja dengan tipe primitif juga. (Maksudku
has_const_begin<int>::value
salah dan tidak menyebabkan kesalahan waktu kompilasi.)Contoh 2
Sekarang kami sedang mencari tanda tangan:
void foo(MyClass&, unsigned)
Harap perhatikan bahwa MyClass tidak harus dapat dibangun secara default atau untuk memenuhi konsep khusus apa pun. Teknik ini bekerja dengan anggota template, juga.
Saya tidak sabar menunggu pendapat tentang ini.
sumber
Sekarang ini menyenangkan teka-teki kecil yang - pertanyaan bagus!
Inilah alternatif untuk solusi Nicola Bonelli yang tidak bergantung pada
typeof
operator yang tidak standar .Sayangnya, ini tidak bekerja pada GCC (MinGW) 3.4.5 atau Digital Mars 8.42n, tetapi ini bekerja pada semua versi MSVC (termasuk VC6) dan pada Comeau C ++.
Blok komentar yang lebih panjang memiliki detail tentang cara kerjanya (atau seharusnya berfungsi). Seperti yang dikatakan, saya tidak yakin perilaku mana yang sesuai standar - saya akan menyambut komentar tentang itu.
pembaruan - 7 Nov 2008:
Sepertinya kode ini benar secara sintaksis, perilaku yang ditunjukkan oleh MSVC dan Comeau C ++ tidak mengikuti standar (terima kasih kepada Leon Timmermans dan litb karena telah mengarahkan saya ke arah yang benar). Standar C ++ 03 mengatakan yang berikut:
Jadi, sepertinya ketika MSVC atau Comeau mempertimbangkan
toString()
fungsi anggotaT
melakukan pencarian nama di situs panggilan didoToString()
ketika template dibuat, itu tidak benar (meskipun sebenarnya perilaku yang saya cari dalam kasus ini).Perilaku GCC dan Digital Mars terlihat benar - dalam kedua kasus,
toString()
fungsi non-anggota terikat pada panggilan.Tikus - Saya pikir saya mungkin telah menemukan solusi yang cerdas, alih-alih saya menemukan beberapa bug penyusun ...
sumber
Solusi C ++ standar yang disajikan di sini oleh litb tidak akan berfungsi seperti yang diharapkan jika metode tersebut akan didefinisikan dalam kelas dasar.
Untuk solusi yang menangani situasi ini lihat:
Dalam bahasa Rusia: http://www.rsdn.ru/forum/message/2759773.1.aspx
Terjemahan bahasa Inggris oleh Roman.Perepelitsa: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1
Ini sangat pintar. Namun satu masalah dengan solutiion ini adalah yang memberikan kesalahan kompiler jika tipe yang diuji adalah yang tidak dapat digunakan sebagai kelas dasar (misalnya tipe primitif)
Dalam Visual Studio, saya perhatikan bahwa jika bekerja dengan metode yang tidak memiliki argumen, sepasang tambahan redundan () perlu dimasukkan di sekitar argumen untuk menyimpulkan () dalam ukuran ekspresi.
sumber
struct g { void f(); private: void f(int); };
karena salah satu fungsi bersifat pribadi (ini karena kodnyausing g::f;
, yang membuatnya gagal jika adaf
yang tidak dapat diakses).MSVC memiliki kata kunci __if_exists dan __if_not_exists ( Doc ). Bersama-sama dengan pendekatan typeof-SFINAE dari Nicola saya bisa membuat pemeriksaan untuk GCC dan MSVC seperti yang dicari OP.
Pembaruan: Sumber dapat ditemukan Di Sini
sumber
Contoh menggunakan SFINAE dan spesialisasi templat sebagian, dengan menulis
Has_foo
cek konsep:sumber
Saya memodifikasi solusi yang disediakan di https://stackoverflow.com/a/264088/2712152 untuk membuatnya sedikit lebih umum. Juga karena tidak menggunakan salah satu fitur C ++ 11 yang baru, kita dapat menggunakannya dengan kompiler lama dan juga harus bekerja dengan msvc. Tetapi kompiler harus memungkinkan C99 untuk menggunakan ini karena ia menggunakan makro variadic.
Makro berikut dapat digunakan untuk memeriksa apakah kelas tertentu memiliki typedef tertentu atau tidak.
Makro berikut dapat digunakan untuk memeriksa apakah kelas tertentu memiliki fungsi anggota tertentu atau tidak dengan sejumlah argumen yang diberikan.
Kita dapat menggunakan 2 makro di atas untuk melakukan pemeriksaan has_typedef dan has_mem_func sebagai:
sumber
HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... );
...template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
Tidak ada yang aneh menyarankan trik bagus berikut yang pernah saya lihat di situs ini:
Anda harus memastikan T adalah kelas. Tampaknya ambiguitas dalam pencarian foo adalah kegagalan substitusi. Saya membuatnya bekerja pada gcc, tidak yakin apakah itu standar.
sumber
Templat generik yang dapat digunakan untuk memeriksa apakah beberapa "fitur" didukung oleh jenis:
Templat yang memeriksa apakah ada metode
foo
yang kompatibel dengan tanda tangandouble(const char*)
Contohnya
http://coliru.stacked-crooked.com/a/83c6a631ed42cea4
sumber
has_foo
memasukkan panggilan template keis_supported
. Apa yang saya ingin adalah untuk memanggil sesuatu seperti:std::cout << is_supported<magic.foo(), struct1>::value << std::endl;
. Alasan untuk ini, saya ingin mendefinisikanhas_foo
untuk setiap tanda tangan fungsi yang berbeda yang ingin saya periksa sebelum saya dapat memeriksa fungsinya?Bagaimana dengan solusi ini?
sumber
toString
kelebihan beban, seperti&U::toString
ambigu.Ada banyak jawaban di sini, tetapi saya gagal, untuk menemukan versi, yang melakukan pemesanan resolusi metode nyata , sementara tidak menggunakan fitur c ++ yang lebih baru (hanya menggunakan fitur c ++ 98).
Catatan: Versi ini diuji dan bekerja dengan vc ++ 2013, g ++ 5.2.0 dan kompilator onlline.
Jadi saya datang dengan versi, yang hanya menggunakan sizeof ():
Demo langsung (dengan pemeriksaan tipe pengembalian yang diperluas dan penyelesaian vc ++ 2010): http://cpp.sh/5b2vs
Tidak ada sumber, karena saya sendiri yang membuatnya.
Saat menjalankan demo Langsung pada kompiler g ++, harap perhatikan bahwa ukuran array 0 diperbolehkan, artinya static_assert yang digunakan tidak akan memicu kesalahan kompiler, bahkan ketika gagal.
Solusi yang umum digunakan adalah mengganti 'typedef' di makro dengan 'extern'.
sumber
static_assert(false);
). Saya menggunakan ini sehubungan dengan CRTP di mana saya ingin menentukan apakah kelas turunan memiliki fungsi tertentu - yang ternyata tidak berfungsi, namun pernyataan Anda selalu berlalu. Saya kehilangan beberapa rambut untuk yang satu itu.Berikut ini adalah versi saya yang menangani semua kemungkinan fungsi anggota berlebih dengan arity sewenang-wenang, termasuk fungsi anggota templat, mungkin dengan argumen default. Ini membedakan 3 skenario yang saling eksklusif ketika membuat panggilan fungsi anggota ke beberapa tipe kelas, dengan tipe arg yang diberikan: (1) valid, atau (2) ambigu, atau (3) tidak dapat berjalan. Contoh penggunaan:
Sekarang Anda dapat menggunakannya seperti ini:
Berikut adalah kode, ditulis dalam c ++ 11, namun, Anda dapat dengan mudah port (dengan tweak kecil) ke non-c ++ 11 yang memiliki ekstensi typeof (misalnya gcc). Anda dapat mengganti makro HAS_MEM dengan milik Anda.
sumber
Anda dapat melewati semua metaprogramming di C ++ 14, dan cukup tulis ini menggunakan
fit::conditional
dari Fit library:Anda juga dapat membuat fungsi langsung dari lambdas juga:
Namun, jika Anda menggunakan kompiler yang tidak mendukung lambda generik, Anda harus menulis objek fungsi terpisah:
sumber
fit
atau perpustakaan lain selain standar?Dengan C ++ 20 Anda dapat menulis yang berikut ini:
sumber
Ini adalah contoh dari kode yang berfungsi.
toStringFn<T>* = nullptr
akan mengaktifkan fungsi yang membutuhkanint
argumen ekstra yang memiliki prioritas lebih dari fungsi yang dibutuhkanlong
saat dipanggil dengan0
.Anda dapat menggunakan prinsip yang sama untuk fungsi yang kembali
true
jika fungsi diimplementasikan.sumber
Saya punya masalah serupa:
Kelas templat yang mungkin diturunkan dari beberapa kelas dasar, beberapa yang memiliki anggota tertentu dan yang lain tidak.
Saya memecahkannya mirip dengan jawaban "typeof" (Nicola Bonelli), tetapi dengan decltype sehingga ia mengkompilasi dan berjalan dengan benar di MSVS:
sumber
Satu lagi cara untuk melakukannya di C ++ 17 (terinspirasi oleh boost: hana).
Tulis satu kali dan gunakan berkali-kali. Itu tidak memerlukan
has_something<T>
kelas tipe ciri.Contoh
sumber
sumber