
Ringkasan bagian sebelumnya
Karena pembatasan pada kemampuan untuk menggunakan kompiler C ++ 11, dan dari kurangnya alternatif, dorongan ingin menulis implementasi sendiri dari pustaka C ++ 11 standar di atas pustaka C ++ 98 / C ++ 03 yang disertakan dengan kompiler.
Static_assert ,
noexcept ,
countof diimplementasikan, dan juga, setelah mempertimbangkan semua definisi non-standar dan fitur kompiler, informasi muncul tentang fungsi yang didukung oleh kompiler saat ini. Implementasinya sendiri dari
nullptr disertakan , yang dipilih pada tahap kompilasi.
Waktunya telah tiba untuk
type_traits dan semua "keajaiban templat khusus" ini. Pada bagian sebelumnya dari bab ini, kami memeriksa implementasi templat dasar dari pustaka standar, dan pada bagian ini kita akan berbicara tentang kombinasi teknik SFINAE dengan templat dan sedikit tentang pembuatan kode.
Tautan ke GitHub dengan hasil untuk hari ini untuk yang tidak sabar dan yang bukan pembaca:
Komitmen dan kritik yang membangun dipersilahkan
Lebih banyak template C ++ di bawah cat.
Daftar isi
PendahuluanBab 1. Viam supervadet vadensBab 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endifBab 3. Menemukan implementasi nullptr yang sempurnaBab 4. C ++ Template Magic....
4.1 Kita mulai dari yang kecil....
4.2 Tentang berapa banyak kesalahan ajaib yang dikompilasi oleh log untuk kita....
4.3 Pointer dan semuanya....
4.4 Apa lagi yang diperlukan untuk pustaka templatBab 5
...
Bab 4. Templat "sihir" C ++. Lanjutan
4.3 Pointer dan semuanya
Pada tahap ini, saya hanya bisa mendapatkan informasi tentang apakah jenisnya adalah array untuk
std :: is_array dan dimungkinkan untuk memulai template untuk pointer. Implementasinya juga sepele, tetapi bukan tanpa asumsi.
Spesialisasi templat sederhana untuk array dengan panjang tertentu βmenangkapβ semua jenis array, namun, masalah muncul dengan tipe
T [] yang tidak lengkap (sebuah array tanpa menentukan panjangnya). Faktanya adalah bahwa tipe ini tidak didefinisikan oleh beberapa kompiler (C ++ Builder) ketika mengkhususkan template, dan saya belum menemukan solusi universal di sini.
Setelah perpustakaan "diajarkan" untuk menentukan tipe bawaan, penyelarasan dalam memori tipe, bekerja dengan pengubah tipe dan hal-hal dasar lainnya melalui templat pada waktu kompilasi, tiba saatnya untuk pointer dan referensi.

Dalam C ++, dua kelompok pointer dapat dibedakan - pointer ke anggota kelas dan pointer ke objek lain. Mengapa pemisahan ini penting untuk implementasi lebih lanjut dari perpustakaan standar? Faktanya adalah bahwa pointer ke anggota kelas memiliki perbedaan yang signifikan dari petunjuk lainnya dengan kehadiran
ini , yaitu. arahkan ke objek kelas ini. Dan secara standar, pointer ke anggota kelas memiliki sintaks yang terpisah untuk mendefinisikan, adalah tipe yang terpisah, dan tidak dapat diwakili melalui pointer biasa. Dalam praktiknya, ini diterjemahkan ke dalam fakta bahwa ukuran pointer ke anggota kelas biasanya lebih besar dari ukuran pointer biasa (yang
== sizeof (void *) ), karena untuk mengimplementasikan fungsi anggota virtual kelas, serta untuk menyimpan pointer
ini , kompiler biasanya mengimplementasikan pointer ke anggota kelas sebagai struktur (baca
tentang fungsi dan
struktur virtual). Cara untuk mempresentasikan pointer kepada anggota kelas dibiarkan, menurut standar, berdasarkan kebijaksanaan kompiler, tetapi kita akan mengingat perbedaan dalam ukuran dan presentasi ketika mempertimbangkan kode lebih lanjut.
Untuk mendefinisikan pointer reguler ke objek, kita akan menulis templat
is_pointer sederhana, serta templat
is_lvalue_reference untuk referensi objek (
kita menyisihkan is_rvalue_reference, karena hingga standar ke-11 tidak ada operator
&& serta operator semantik keseluruhan):
namespace detail { template<class> struct _is_pointer_helper : public false_type { }; template<class _Tp> struct _is_pointer_helper<_Tp*> : public true_type { }; }
Tidak ada lagi sesuatu yang secara fundamental baru di sini, semua hal yang sama dilakukan di bagian sebelumnya bab ini. Mari kita lanjutkan mendefinisikan pointer ke objek - sekarang mari kita lihat pointer ke fungsi.
Penting untuk memahami bahwa fungsi dan fungsi anggota suatu kelas adalah entitas yang sepenuhnya berbeda sesuai dengan standar:
- Pointer pertama akan normal (pointer ke objek), yang kedua akan memiliki pointer ke anggota kelas.
void (*func_ptr)(int);
- Anda dapat membuat tautan ke yang pertama (tautan objek), tetapi Anda tidak dapat membuat tautan kedua.
void (&func_ref)(int);
Di sini saya hanya akan menyebutkan sedikit tentang pembuatan kode. Karena sebelum C ++ 11 tidak ada templat dengan sejumlah variabel parameter, semua templat di mana mungkin ada sejumlah parameter yang berbeda
ditentukan melalui spesialisasi templat utama dengan sejumlah besar parameter pada input dan inisialisasi mereka dengan parameter dummy default. Hal yang sama berlaku untuk kelebihan fungsi, seperti Tidak ada makro dengan sejumlah parameter variabel juga. Sejak menulis 60-70 baris dengan jenis spesialisasi templat yang sama dengan tangan Anda, fungsi overloading adalah tugas yang agak membosankan dan tidak berguna, dan juga penuh dengan kemungkinan membuat kesalahan. Saya menulis generator kode sederhana untuk template dan fungsi overload untuk tujuan ini. Saya memutuskan untuk membatasi diri untuk mendefinisikan fungsi hingga 24 parameter dan ini terlihat agak rumit dalam kode, tetapi sederhana dan jelas:
namespace detail { template <class R> struct _is_function_ptr_helper : false_type {}; template <class R > struct _is_function_ptr_helper<R(*)()> : true_type {}; template <class R > struct _is_function_ptr_helper<R(*)(...)> : true_type {}; template <class R, class T0> struct _is_function_ptr_helper<R(*)(T0)> : true_type {}; template <class R, class T0> struct _is_function_ptr_helper<R(*)(T0 ...)> : true_type {};
... template <class R, class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16, class T17, class T18, class T19, class T20, class T21, class T22, class T23, class T24> struct _is_function_ptr_helper<R(*)(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24)> : true_type {}; template <class R, class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16, class T17, class T18, class T19, class T20, class T21, class T22, class T23, class T24> struct _is_function_ptr_helper<R(*)(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24 ...)> : true_type {}; }
Kami mendefinisikan tipe-tipe yang kami kenal dari bab sebelumnya untuk teknik SFINAE:
namespace detail {
Beberapa makro lagi untuk kenyamanan namespace detail { #define _IS_MEM_FUN_PTR_CLR \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS)); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...)); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS) const); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS) volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS) const volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...) const); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...) volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...) const volatile); #ifdef _STDEX_CDECL _no_type _STDEX_CDECL _is_mem_function_ptr(...); #define _IS_MEM_FUN_CDECL_PTR \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS)); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS) const); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS) volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS) const volatile); #define _IS_MEM_FUN_STDCALL_PTR \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS)); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS) const); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS) volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS) const volatile); #define _IS_MEM_FUN_FASTCALL_PTR \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS)); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS) const); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS) volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS) const volatile); #else _no_type _is_mem_function_ptr(...); #define _IS_MEM_FUN_CDECL_PTR #define _IS_MEM_FUN_STDCALL_PTR #define _IS_MEM_FUN_FASTCALL_PTR #endif #define _IS_MEM_FUN_PTR \ _IS_MEM_FUN_PTR_CLR \ _IS_MEM_FUN_CDECL_PTR \ _IS_MEM_FUN_STDCALL_PTR \ _IS_MEM_FUN_FASTCALL_PTR }
Makro didefinisikan sehingga relatif mudah untuk mendefinisikan kembali
TIPE dan definisi
ARGS sebagai daftar jenis dan parameter, kemudian mengganti makro
_IS_MEM_FUN_PTR untuk menghasilkan definisi untuk semua jenis fungsi yang mungkin oleh preprocessor. Perlu juga memperhatikan fakta bahwa untuk kompiler Microsoft,
panggilan perjanjian (
__ fastcall ,
__stdcall dan
__cdecl ) juga
penting , karena dengan konvensi yang berbeda, fungsinya akan berbeda, meskipun mereka memiliki serangkaian argumen dan nilai pengembalian yang sama. Sebagai hasilnya, seluruh desain makro muluk ini digunakan dengan cukup kompak:
namespace detail { #define TYPES #define ARGS _IS_MEM_FUN_PTR #undef TYPES #undef ARGS #define TYPES , class T0 #define ARGS T0 _IS_MEM_FUN_PTR #undef TYPES #undef ARGS #define TYPES , class T0, class T1 #define ARGS T0, T1 _IS_MEM_FUN_PTR #undef TYPES #undef ARGS
... #define TYPES , class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16, class T17, class T18, class T19, class T20, class T21, class T22, class T23, class T24 #define ARGS T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24 _IS_MEM_FUN_PTR #undef TYPES #undef ARGS
Dan sekarang untuk apa semua itu ditulis:
namespace detail { template <class _Tp, bool _IsRef> struct _is_mem_function_ptr_impl { static _Tp *p; static const bool value = (sizeof(_is_mem_function_ptr(_is_mem_function_ptr_impl::p)) == sizeof(_yes_type)); typedef typename integral_constant<bool, _is_mem_function_ptr_impl::value == bool(true)>::type type; }; template <class _Tp> struct _is_mem_function_ptr_impl<_Tp, true>: public false_type {}; template <class _Tp> struct _is_mem_function_ptr_helper: public _is_mem_function_ptr_impl<_Tp, is_reference<_Tp>::value>::type {}; template <class _Tp, bool _IsMemberFunctionPtr> struct _is_function_chooser_impl : public false_type { }; template <class _Tp> struct _is_function_chooser_impl<_Tp, false> : public _is_function_ptr_helper<_Tp*> { }; template<class _Tp, bool _IsRef = true> struct _is_function_chooser : public false_type { }; template <class _Tp> struct _is_function_chooser<_Tp, false> { static const bool value = _is_function_chooser_impl<_Tp, _is_mem_function_ptr_helper<_Tp>::value>::value; }; }
Untuk memeriksa apakah suatu tipe adalah fungsi anggota dari suatu kelas, ia pertama-tama diperiksa untuk melihat apakah tipe itu adalah suatu rujukan. Kemudian pointer dari tipe ini dibuat dan diganti menjadi fungsi probe. Menggunakan teknik SFINAE, kompiler memilih kelebihan fungsi probe yang diperlukan untuk pointer tersebut dan, berdasarkan hasil perbandingan dengan
_yes_type, membentuk hasilnya.
Berdasarkan pemeriksaan pada fungsi anggota kelas, pemeriksaan tipe ditulis pada apakah itu termasuk dalam tipe fungsi. Kami memeriksa apakah jenisnya adalah referensi, jika tidak, maka kami mencari spesialisasi yang sesuai dari struktur templat
penelusuran untuk penunjuk jenis ini, yang akan menjadi
true_type untuk setiap fungsi pointer dengan hingga 24 parameter.
Dan sekarang kita menggunakan hasilnya untuk mengimplementasikan
is_function . Di sini, untuk alasan yang sama seperti pada bagian
sebelumnya , saya tidak dapat mewarisi struktur ini dari
integral_constant , sehingga perilaku
integral_constant "disimulasikan".
Dan untuk mengimplementasikan
is_member_function_pointer masih lebih sederhana:
Lebih lanjut, berdasarkan pola-pola ini, kita dapat menentukan apakah tipe tersebut adalah anggota kelas pada prinsipnya:
namespace detail { template<class _Tp> struct _is_member_object_pointer_impl1 : public _not_< _or_<_is_function_ptr_helper<_Tp>, _is_mem_function_ptr_helper<_Tp> > >::type { }; template<class _Tp> struct _is_member_object_pointer_impl2 : public false_type { }; template<class _Tp, class _Cp> struct _is_member_object_pointer_impl2<_Tp _Cp::*> : public true_type { }; template<class _Tp> struct _is_member_object_pointer_helper: public _and_<_is_member_object_pointer_impl1<_Tp>, _is_member_object_pointer_impl2<_Tp> >::type {}; }
Digunakan operasi logis 'dan', 'atau', 'bukan' pada jenis dari bagian pertama namespace detail { struct void_type {};
Di sini kami menggunakan operasi logis pada tipe yang, dengan bantuan template
bersyarat , akhirnya memilih tipe template yang sesuai. Pemrograman templat dengan segala kemuliaan, sebagai akibatnya, pada tahap kompilasi, kami telah memiliki informasi tentang apakah tipe tersebut adalah anggota kelas. Cukup "geram", tetapi betapa spektakuler dan efektifnya!
Sedikit lebih banyak pemrograman template murni pada elemen logika yang sama dan kami memiliki
is_fundamental ,
is_compound , dll. tanda (ini menyenangkan saya, tetapi Anda?):
Pembaca yang penuh perhatian akan memperhatikan bahwa definisi is_enum dikomentari. Faktanya adalah bahwa saya tidak menemukan cara untuk membedakan enum dari tipe lain, tapi saya pikir ini layak tanpa menggunakan macro yang bergantung pada compiler. Mungkin seorang pembaca yang penuh perhatian dan berpengetahuan akan memberi tahu Anda cara atau cara berpikir Anda dalam hal ini.
Untuk menentukan fakta bahwa suatu tipe adalah kelas, tidak diperlukan lagi sekarang:
namespace detail { template <class _Tp, bool _IsReference> struct _is_class_helper { typedef integral_constant<bool, false> type; }; template <class _Tp> struct _is_class_helper<_Tp, false> { typedef integral_constant<bool, (is_scalar<_Tp>::value == bool(false))
Dan semuanya akan baik-baik saja, tetapi
penyatuan dalam C ++ tidak dapat dibedakan dari kelas dalam kasus umum. Karena mereka sangat mirip dalam "manifestasi eksternal" mereka, dan saya tidak dapat memverifikasi perbedaan (misalnya, ketidakmampuan untuk mewarisi dari
persatuan ) tanpa kesalahan kompilasi. Mungkin seseorang akan memberi tahu Anda manuver rumit untuk menentukan
penyatuan saat kompilasi, maka
is_class akan sesuai dengan standar.
Di bagian
akhir bab ini, saya akan berbicara tentang bagaimana
std :: decay dan
std :: common_type diimplementasikan , serta apa yang masih harus ditambahkan ke
type_traits .
Terima kasih atas perhatian anda