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

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.

Tautan ke GitHub dengan hasil untuk hari ini untuk yang tidak sabar dan yang bukan pembaca:

Komitmen dan kritik yang membangun dipersilahkan

Benamkan diri Anda di dunia "templat ajaib" C ++.

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. C ++ Template Magic


Setelah selesai dengan kata kunci C ++ 11 dan semua "sakelar" yang bergantung pada definisi di antara implementasinya, saya mulai mengisi type_traits . Sebenarnya, saya sudah memiliki beberapa kelas template, mirip dengan yang standar, yang sudah bekerja dalam proyek untuk waktu yang lama dan karena itu tetap membawa semua ini ke dalam bentuk yang sama, serta menambahkan fungsi yang hilang.

gambar Jujur, saya terinspirasi oleh pemrograman template. Terutama kesadaran bahwa semua ini adalah berbagai pilihan: perhitungan, percabangan kode, kondisi, pengecekan kesalahan dilakukan selama proses kompilasi dan tidak ada biaya program akhir saat dijalankan. Dan karena templat dalam C ++ pada dasarnya adalah bahasa pemrograman Turing-complete , saya mengantisipasi betapa elegan dan relatif mudahnya mungkin untuk mengimplementasikan bagian standar yang terkait dengan pemrograman pada templat. Tetapi, untuk segera menghancurkan semua ilusi, saya akan mengatakan bahwa seluruh teori kelengkapan Turing dipecah menjadi implementasi konkret templat dalam kompiler. Dan bagian penulisan perpustakaan ini, bukannya solusi elegan dan "trik" pemrograman template, berubah menjadi perjuangan sengit dengan kompiler, sementara masing-masing "runtuh" ​​dengan caranya sendiri, dan ada baiknya jika masuk ke kesalahan kompiler internal yang cadel, atau bahkan jatuh dengan keras dengan pengecualian tidak tertangani. GCC (g ++) menunjukkan dirinya sendiri yang terbaik, yang dengan tenang “mengunyah” semua konstruksi templat dan hanya dikutuk (dalam kasus ini) di tempat-tempat di mana tidak ada nama ketik yang eksplisit.

4.1 Mulai dari yang kecil


Saya mulai dengan template sederhana untuk std :: integral_constant , std :: bool_constant dan template kecil serupa.

template<class _Tp, _Tp Val> struct integral_constant { // convenient template for integral constant types static const _Tp value = Val; typedef const _Tp value_type; typedef integral_constant<_Tp, Val> type; operator value_type() const { // return stored value return (value); } value_type operator()() const { // return stored value return (value); } }; typedef integral_constant<bool, true> true_type; typedef integral_constant<bool, false> false_type; template<bool Val> struct bool_constant : public integral_constant<bool, Val> {}; // Primary template. // Define a member typedef @c type to one of two argument types. template<bool _Cond, class _Iftrue, class _Iffalse> struct conditional { typedef _Iftrue type; }; // Partial specialization for false. template<class _Iftrue, class _Iffalse> struct conditional<false, _Iftrue, _Iffalse> { typedef _Iffalse type; }; 

Berdasarkan pada kondisi, Anda dapat memasukkan templat yang sesuai untuk operasi logis {"dan", "atau", "tidak"} lebih dari tipe (Dan semua operasi ini dianggap tepat pada tahap kompilasi! Ini hebat, bukan?):

 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); } }; } 

Tiga hal yang patut diperhatikan di sini:

1) Penting untuk selalu menempatkan spasi di antara kurung sudut ('<' dan '>') dari templat, karena sebelum C ++ 11 tidak ada klarifikasi dalam standar tentang cara menafsirkan '>>' dan '<<' dalam kode seperti _atau _ <_ B2, _atau _ <_ B3, _B4 >> , dan karenanya hampir semua kompiler memperlakukan ini sebagai operator bit shift, yang mengarah ke kesalahan kompilasi.

2) Dalam beberapa kompiler (Visual Studio 6.0 misalnya) ada bug yang mengandung fakta bahwa tidak mungkin untuk menggunakan tipe void sebagai parameter templat. Untuk tujuan ini, tipe void_type yang terpisah diperkenalkan pada bagian di atas untuk menggantikan tipe void di mana nilai parameter templat default diperlukan.

3) Kompiler yang sangat lama (Borland C ++ Builder misalnya) memiliki tipe bool yang diimplementasikan secara bengkok , yang dalam beberapa situasi "tiba-tiba" berubah menjadi int ( true -> 1, false -> 0), serta jenis variabel statis konstan dari tipe tersebut. bool (dan bukan hanya mereka), jika mereka terkandung dalam kelas template. Karena semua kekacauan ini, sebagai akibatnya, untuk perbandingan yang benar-benar tidak berbahaya dalam gaya my_template_type :: static_bool_value == false, kompiler dapat dengan mudah mengeluarkan sebuah mempesona tidak dapat melemparkan 'tipe tidak terdefinisi' ke int (0) atau sesuatu seperti itu. Oleh karena itu, perlu untuk selalu mencoba untuk secara eksplisit menunjukkan jenis nilai untuk perbandingan, dengan demikian membantu kompiler menentukan jenis mana yang berhadapan dengannya.


Tambahkan lebih banyak pekerjaan dengan nilai const dan volatile . Pertama, remove_ yang diterapkan secara sepele ... di mana kami hanya mengkhususkan templat untuk pengubah jenis tertentu - jika jenis dengan pengubah masuk ke dalam templat, kompiler harus, setelah melihat semua spesialisasi (ingat prinsip SFINAE dari bab sebelumnya ) dari templat, pilih yang paling cocok (dengan indikasi eksplisit dari pengubah yang diinginkan) :

 template<class _Tp> struct is_function; template<class _Tp> struct remove_const { // remove top level const qualifier typedef _Tp type; }; template<class _Tp> struct remove_const<const _Tp> { // remove top level const qualifier typedef _Tp type; }; template<class _Tp> struct remove_const<const volatile _Tp> { // remove top level const qualifier typedef volatile _Tp type; }; // remove_volatile template<class _Tp> struct remove_volatile { // remove top level volatile qualifier typedef _Tp type; }; template<class _Tp> struct remove_volatile<volatile _Tp> { // remove top level volatile qualifier typedef _Tp type; }; // remove_cv template<class _Tp> struct remove_cv { // remove top level const and volatile qualifiers typedef typename remove_const<typename remove_volatile<_Tp>::type>::type type; }; 

Dan kemudian kita menerapkan template add_ ... di mana semuanya sudah sedikit lebih rumit:

 namespace detail { template<class _Tp, bool _IsFunction> struct _add_const_helper { typedef _Tp const type; }; template<class _Tp> struct _add_const_helper<_Tp, true> { typedef _Tp type; }; template<class _Tp, bool _IsFunction> struct _add_volatile_helper { typedef _Tp volatile type; }; template<class _Tp> struct _add_volatile_helper<_Tp, true> { typedef _Tp type; }; template<class _Tp, bool _IsFunction> struct _add_cv_helper { typedef _Tp const volatile type; }; template<class _Tp> struct _add_cv_helper<_Tp, true> { typedef _Tp type; }; } // add_const template<class _Tp> struct add_const: public detail::_add_const_helper<_Tp, is_function<_Tp>::value> { }; template<class _Tp> struct add_const<_Tp&> { typedef _Tp & type; }; // add_volatile template<class _Tp> struct add_volatile : public detail::_add_volatile_helper<_Tp, is_function<_Tp>::value> { }; template<class _Tp> struct add_volatile<_Tp&> { typedef _Tp & type; }; // add_cv template<class _Tp> struct add_cv : public detail::_add_cv_helper<_Tp, is_function<_Tp>::value> { }; template<class _Tp> struct add_cv<_Tp&> { typedef _Tp & type; }; 

Di sini kami hati-hati memproses jenis referensi secara terpisah agar tidak kehilangan tautan. Selain itu, kami tidak akan lupa tentang jenis fungsi yang tidak mungkin untuk membuat volatile atau const pada prinsipnya, oleh karena itu kami akan membiarkannya “apa adanya”. Saya dapat mengatakan bahwa semua ini terlihat sangat sederhana, tetapi inilah yang terjadi ketika "iblis ada dalam rincian", atau lebih tepatnya, "bug ada dalam detail implementasi."

Akhir dari bagian pertama dari bab keempat. Pada bagian kedua saya akan berbicara tentang bagaimana pemrograman template keras diberikan kepada kompiler, dan juga akan ada templat magic yang lebih keren. Ah, namun - mengapa lama tidak konstanta integral, menurut beberapa kompiler sampai hari ini.

Terima kasih atas perhatian anda

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


All Articles