Bagaimana saya menulis library C ++ 11 standar atau mengapa boost sangat menakutkan. Bab 4.3

Kami melanjutkan petualangan.

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


Pendahuluan
Bab 1. Viam supervadet vadens
Bab 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
Bab 3. Menemukan implementasi nullptr yang sempurna
Bab 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 templat
Bab 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.

// is_array template<class> struct is_array : public false_type { }; template<class _Tp, std::size_t _Size> struct is_array<_Tp[_Size]> : public true_type { }; /*template<class _Tp> struct is_array<_Tp[]>: public true_type { }; */ 

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.

gambar 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 { }; } // is_pointer template<class _Tp> struct is_pointer : public detail::_is_pointer_helper<typename remove_cv<_Tp>::type>::type { }; // is_lvalue_reference template<class> struct is_lvalue_reference : public false_type { }; template<class _Tp> struct is_lvalue_reference<_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); //  'func_ptr'    'void func(int){}' void (ClassType::*mem_func_ptr)(int); //  'mem_func_ptr'  -  'ClassType'  'void ClassType::func(int){}' 

  • Anda dapat membuat tautan ke yang pertama (tautan objek), tetapi Anda tidak dapat membuat tautan kedua.

 void (&func_ref)(int); //  'func_ref'    'void func(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 { // SFINAE magic typedef char _yes_type; struct _no_type { char padding[8]; }; } 

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 //      define  : #undef _IS_MEM_FUN_PTR #undef _IS_MEM_FUN_PTR_CLR #undef _IS_MEM_FUN_CDECL_PTR #undef _IS_MEM_FUN_STDCALL_PTR #undef _IS_MEM_FUN_FASTCALL_PTR } 

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".

 // is_function template<class _Tp> struct is_function { static const bool value = detail::_is_function_chooser<_Tp, is_reference<_Tp>::value>::value; typedef const bool value_type; typedef integral_constant<bool, is_function::value == bool(true)> type; operator value_type() const { // return stored value return (value); } value_type operator()() const { // return stored value return (value); } }; 

Dan untuk mengimplementasikan is_member_function_pointer masih lebih sederhana:

 // is_member_function_pointer template<class _Tp> struct is_member_function_pointer : public detail::_is_mem_function_ptr_helper<typename remove_cv<_Tp>::type>::type { }; 

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 {}; } // is_member_object_pointer template<class _Tp> struct is_member_object_pointer : public detail::_is_member_object_pointer_helper<typename remove_cv<_Tp>::type>::type { }; 

Digunakan operasi logis 'dan', 'atau', 'bukan' pada jenis dari bagian pertama
 namespace detail { struct void_type {}; //typedef void void_type; template<class _B1 = void_type, class _B2 = void_type, class _B3 = void_type, class _B4 = void_type> struct _or_ : public conditional<_B1::value, _B1, _or_<_B2, _or_<_B3, _B4> > >::type { }; template<> struct _or_<void_type, void_type, void_type, void_type>; template<class _B1> struct _or_<_B1, void_type, void_type, void_type> : public _B1 { }; template<class _B1, class _B2> struct _or_<_B1, _B2, void_type, void_type> : public conditional<_B1::value, _B1, _B2>::type { }; template<class _B1, class _B2, class _B3> struct _or_<_B1, _B2, _B3, void_type> : public conditional<_B1::value, _B1, _or_<_B2, _B3> >::type { }; template<class _B1 = void_type, class _B2 = void_type, class _B3 = void_type, class _B4 = void_type> struct _and_; template<> struct _and_<void_type, void_type, void_type, void_type>; template<class _B1> struct _and_<_B1, void_type, void_type, void_type> : public _B1 { }; template<class _B1, class _B2> struct _and_<_B1, _B2, void_type, void_type> : public conditional<_B1::value, _B2, _B1>::type { }; template<class _B1, class _B2, class _B3> struct _and_<_B1, _B2, _B3, void_type> : public conditional<_B1::value, _and_<_B2, _B3>, _B1>::type { }; template<class _Pp> struct _not_ { static const bool value = !bool(_Pp::value); typedef const bool value_type; typedef integral_constant<bool, _not_::value == bool(true)> type; operator value_type() const { // return stored value return (value); } value_type operator()() const { // return stored value return (value); } }; } 


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?):

 // is_arithmetic template<class _Tp> struct is_arithmetic : public detail::_or_<is_integral<_Tp>, is_floating_point<_Tp> >::type { }; // is_fundamental template<class _Tp> struct is_fundamental : public detail::_or_<is_arithmetic<_Tp>, is_void<_Tp>, is_null_pointer<_Tp> >::type {}; // is_object template<class _Tp> struct is_object : public detail::_not_< detail::_or_< is_function<_Tp>, is_reference<_Tp>, is_void<_Tp> > >::type {}; // is_scalar template<class _Tp> struct is_scalar : public detail::_or_<is_arithmetic<_Tp>, is_pointer<_Tp>, is_member_pointer<_Tp>, is_null_pointer<_Tp>/*, is_enum<_Tp>*/ >::type {}; // is_compound template<class _Tp> struct is_compound: public detail::_not_<is_fundamental<_Tp> >::type { }; 
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)) //&& !is_union<_Tp>::value >::value && (is_array<_Tp>::value == bool(false)) && (is_void<_Tp>::value == bool(false)) && (is_function<_Tp>::value == bool(false))> type; }; } // is_class template<class _Tp> struct is_class : public detail::_is_class_helper<typename remove_cv<_Tp>::type, is_reference<_Tp>::value>::type { }; 

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

Source: https://habr.com/ru/post/id418347/


All Articles