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

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 pertama, kami memeriksa implementasi templat sederhana dari pustaka standar, tetapi sekarang kami akan membahas lebih dalam.

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

Komitmen dan kritik yang membangun dipersilahkan

Kelanjutan pencelupan 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. Templat "sihir" C ++. Lanjutan


4.2 Tentang berapa banyak kesalahan ajaib yang dikompilasi oleh log


Di bagian pertama bab ini, templat type_traits dasar diperkenalkan, tetapi beberapa lagi tidak ada untuk set lengkap.

Sebagai contoh, templat is_integral dan is_floating_point benar -benar diperlukan, yang sebenarnya sangat sepele didefinisikan - melalui spesialisasi templat untuk setiap jenis bawaan . Pertanyaan di sini hanya muncul dengan tipe "besar" yang panjang . Faktanya adalah bahwa tipe ini sebagai bawaan muncul dalam standar bahasa C ++ hanya dari versi 11. Dan akan masuk akal untuk mengasumsikan bahwa semuanya datang untuk memeriksa versi standar C ++ (yang secara unik sulit ditentukan ), tetapi tidak ada di sana.

gambar Karena, sejak 1999, standar bahasa C99 C ada di mana tipe int panjang dan tanpa tanda lama telah ada (sejak 1999!), Dan karena bahasa C ++ berusaha mempertahankan kompatibilitas dengan C murni, banyak kompiler (yang biasanya dicampur C / C ++) baru saja menambahkannya sebagai tipe fundamental bahkan sebelum standar C ++ 03 dirilis. Artinya, situasinya adalah bahwa tipe bawaan sebenarnya (dari C), tetapi tidak dijelaskan dalam standar C ++ dan tidak boleh ada di sana. Dan itu menambah sedikit kebingungan pada implementasi perpustakaan standar. Tapi mari kita lihat kodenya:

namespace detail { template <class> struct _is_floating_point : public false_type {}; template<> struct _is_floating_point<float> : public true_type {}; template<> struct _is_floating_point<double> : public true_type {}; template<> struct _is_floating_point<long double> : public true_type {}; } template <class _Tp> struct is_floating_point : public detail::_is_floating_point<typename remove_cv<_Tp>::type> { }; 

Semuanya jelas dengan kode di atas - kami mengkhususkan template untuk tipe floating-point yang diperlukan, dan, setelah "membersihkan" jenis pengubah, kami mengatakan "ya" atau "tidak" untuk jenis yang diteruskan kepada kami. Baris berikutnya adalah tipe integer:

 namespace detail { template <class> struct _is_integral_impl : public false_type {}; template<> struct _is_integral_impl<bool> : public true_type {}; template<> struct _is_integral_impl<char> : public true_type {}; template<> struct _is_integral_impl<wchar_t> : public true_type {}; template<> struct _is_integral_impl<unsigned char> : public true_type {}; template<> struct _is_integral_impl<unsigned short int> : public true_type {}; template<> struct _is_integral_impl<unsigned int> : public true_type {}; template<> struct _is_integral_impl<unsigned long int> : public true_type {}; #ifdef LLONG_MAX template<> struct _is_integral_impl<unsigned long long int> : public true_type {}; #endif template<> struct _is_integral_impl<signed char> : public true_type {}; template<> struct _is_integral_impl<short int> : public true_type {}; template<> struct _is_integral_impl<int> : public true_type {}; template<> struct _is_integral_impl<long int> : public true_type {}; #ifdef LLONG_MAX template<> struct _is_integral_impl<long long int> : public true_type {}; #endif template <class _Tp> struct _is_integral : public _is_integral_impl<_Tp> {}; template<> struct _is_integral<char16_t> : public true_type {}; template<> struct _is_integral<char32_t> : public true_type {}; template<> struct _is_integral<int64_t> : public true_type {}; template<> struct _is_integral<uint64_t> : public true_type {}; } template <class _Tp> struct is_integral : public detail::_is_integral<typename remove_cv<_Tp>::type> { }; 

Di sini Anda perlu berhenti sebentar dan berpikir. Untuk tipe integer "lama" seperti int , bool , dll. kami melakukan spesialisasi yang sama dengan is_floating_point . Untuk tipe " lama" int panjang panjang dan pasangannya yang tidak ditandatangani, kami mendefinisikan kelebihan hanya jika ada definisi LLONG_MAX, yang didefinisikan dalam C ++ 11 (sebagai standar C ++ pertama yang kompatibel dengan C99), dan harus didefinisikan dalam file header climits sebagai maksimum sejumlah besar yang cocok dengan objek bertipe long panjang int . Climits juga memiliki beberapa definisi makro (untuk jumlah sekecil mungkin dan setara yang tidak ditandatangani), tetapi saya memutuskan untuk menggunakan makro ini, yang tidak penting. Yang penting adalah bahwa, tidak seperti dorongan, dalam implementasi ini tipe "besar" dari C tidak akan didefinisikan sebagai konstanta integer, meskipun mereka (mungkin) ada dalam kompiler. Yang lebih penting adalah tipe char16_t dan char32_t , yang juga diperkenalkan di C ++ 11, tetapi mereka tidak dikirimkan di C99 (mereka muncul secara bersamaan dengan C ++ di standar C11), dan oleh karena itu, dalam standar lama, definisi mereka dapat hanya melalui alias tipe (misalnya typedef short char16_t , tetapi lebih lanjut tentang itu nanti). Jika demikian, agar spesialisasi Templat untuk menangani situasi dengan benar ketika jenis ini terpisah (built-in) dan ketika mereka didefinisikan melalui typedef , diperlukan satu lapis detail spesialisasi templat lagi :: _ is_integral .

Fakta yang menarik adalah bahwa dalam beberapa kompiler lama, tipe "besar" C-pemalu ini tidak konstan secara integral . Apa yang dapat dipahami dan bahkan dimaafkan, karena jenis ini non-standar untuk C ++ hingga 11 standar, dan umumnya tidak boleh ada. Tetapi yang sulit untuk dipahami adalah bahwa tipe-tipe ini dalam kompiler C ++ terbaru dari kreativitas Embarcadero (Embarcadero C ++ Builder), yang seharusnya didukung oleh C ++ 11, masih merupakan konstanta yang tidak terpisahkan dalam rakitan 32-bit mereka (seperti 20 tahun yang lalu) maka itu Borland masih benar). Rupanya karena ini, termasuk, sebagian besar pustaka C ++ 11 standar tidak ada di majelis 32-bit ini (#include ratio? Chrono? Will cost). Embarcadero tampaknya telah memutuskan untuk memaksa era 64-bit dengan moto: “Apakah Anda ingin C ++ 11 atau standar yang lebih baru? Bangun program 64-bit (dan hanya bunyi, kompiler kami tidak bisa)! ”

Setelah menyelesaikan proses dengan jenis dasar bahasa, kami memperkenalkan beberapa pola yang lebih sederhana:

Pola sederhana
 template <bool, class _Tp = detail::void_type> struct enable_if { }; template <class _Tp> struct enable_if<true, _Tp> { typedef _Tp type; }; template<class, class> struct is_same : public false_type { }; template<class _Tp> struct is_same<_Tp, _Tp> : public true_type//specialization { }; template <class _Tp> struct is_const : public false_type { }; template <class _Tp> struct is_const<const _Tp> : public true_type { }; template <class _Tp> struct is_const<const volatile _Tp> : public true_type { }; /// is_volatile template<class> struct is_volatile : public false_type { }; template<class _Tp> struct is_volatile<volatile _Tp> : public true_type { }; template<class _Tp> struct is_volatile<const volatile _Tp> : public true_type { }; 


Hanya fakta bahwa templat berspesialisasi untuk semua pengubah tipe ( volatile dan const volatile misalnya) yang perlu diperhatikan di sini, karena beberapa kompiler cenderung “kehilangan” salah satu pengubah ketika memperluas templat.

Secara terpisah, saya menyoroti implementasi is_signed dan is_unsigned :

 namespace detail { template<bool> struct _sign_unsign_chooser; template<class _Tp> struct _signed_comparer { static const bool value = _Tp(-1) < _Tp(0); }; template<class _Tp> struct _unsigned_comparer { static const bool value = _Tp(0) < _Tp(-1); }; template<bool Val> struct _cat_base : integral_constant<bool, Val> { // base class for type predicates }; template<> struct _sign_unsign_chooser<true>//integral { template<class _Tp> struct _signed : public _cat_base<_signed_comparer<typename remove_cv<_Tp>::type>::value> { }; template<class _Tp> struct _unsigned : public _cat_base<_unsigned_comparer<typename remove_cv<_Tp>::type>::value> { }; }; template<> struct _sign_unsign_chooser<false>//floating point { template<class _Tp> struct _signed : public is_floating_point<_Tp> { }; template<class _Tp> struct _unsigned : public false_type { }; }; } template<class T> struct is_signed { // determine whether T is a signed type static const bool value = detail::_sign_unsign_chooser<is_integral<T>::value>::template _signed<T>::value; typedef const bool value_type; typedef integral_constant<bool, is_signed::value == bool(true)> type; operator value_type() const { // return stored value return (value); } value_type operator()() const { // return stored value return (value); } }; template<class T> struct is_unsigned { // determine whether T is an unsigned type static const bool value = detail::_sign_unsign_chooser<is_integral<T>::value>::template _unsigned<T>::value; typedef const bool value_type; typedef integral_constant<bool, is_unsigned::value == bool(true)> type; operator value_type() const { // return stored value return (value); } value_type operator()() const { // return stored value return (value); } }; 

Ketika menerapkan bagian ini, saya memasuki pertempuran yang tidak setara dengan Borland C ++ Builder 6.0, yang tidak ingin membuat dua templat ini pewaris dari integral_constant , yang akhirnya menghasilkan lusinan kesalahan kompiler internal yang “meniru” perilaku integral_constant untuk templat ini. Di sini, mungkin bernilai lebih untuk melawan dan datang dengan apa yang alat penghapusan jenis is_ * un * ditandatangani: integral_constant melalui template, tapi saya ditunda sampai masalah tidak prioritas. Yang menarik di bagian kode di atas adalah bagaimana pada waktu kompilasi ditentukan bahwa jenisnya tidak ditandatangani / ditandatangani. Untuk memulainya, semua jenis non-integer ditandai dan bagi mereka templat pergi ke cabang khusus yang terpisah _sign_unsign_chooser dengan argumen templat false , yang pada gilirannya selalu mengembalikan nilai == false untuk semua jenis kecuali tipe floating-point standar (mereka selalu masuk karena alasan yang jelas, jadi _signed :: value akan benar ). Untuk tipe bilangan bulat, sederhana, namun menghibur, pemeriksaan dilakukan. Di sini kita menggunakan fakta bahwa untuk tipe integer yang tidak ditandatangani, ketika angka menurun dan kemudian melewati minimum (0 jelas), terjadi overflow dan nomor memperoleh nilai maksimum yang mungkin.

Fakta ini sudah diketahui, dan juga fakta bahwa untuk tipe yang ditandatangani melimpah adalah perilaku yang tidak terdefinisi dan Anda harus memperhatikannya (sesuai standar, Anda tidak dapat mengurangi variabel int kurang dari INT_MIN dan berharap bahwa sebagai hasil dari luapan Anda akan mendapatkan INT_MAX , bukan 42 atau hard disk yang diformat. )

Kami menulis _Tp (-1) <_Tp (0) untuk memeriksa tipe "sign" menggunakan fakta ini, kemudian untuk tipe unsigned -1 "mentransformasikan" melalui overflow ke jumlah maksimum dari tipe ini, sementara untuk tipe yang ditandatangani perbandingan seperti itu akan dilakukan tanpa overflow, dan -1 akan dibandingkan dengan 0.

Dan yang terakhir untuk hari ini, tetapi jauh dari "trik" terakhir di perpustakaan saya adalah penerapan alignment_of :

 namespace detail { template <class _Tp> struct _alignment_of_trick { char c; _Tp t; _alignment_of_trick(); }; template <unsigned A, unsigned S> struct _alignment_logic_helper { static const std::size_t value = A < S ? A : S; }; template <unsigned A> struct _alignment_logic_helper<A, 0> { static const std::size_t value = A; }; template <unsigned S> struct _alignment_logic_helper<0, S> { static const std::size_t value = S; }; template< class _Tp > struct _alignment_of_impl { #if _MSC_VER > 1400 // // With MSVC both the build in __alignof operator // and following logic gets things wrong from time to time // Using a combination of the two seems to make the most of a bad job: // static const std::size_t value = (_alignment_logic_helper< sizeof(_alignment_of_trick<_Tp>) - sizeof(_Tp), __alignof(_Tp) >::value); #else static const std::size_t value = (_alignment_logic_helper< sizeof(_alignment_of_trick<_Tp>) - sizeof(_Tp), sizeof(_Tp) >::value); #endif typedef integral_constant<std::size_t, std::size_t(_alignment_of_impl::value)> type; private: typedef intern::type_traits_asserts check; typedef typename check::alignment_of_type_can_not_be_zero_assert< _alignment_of_impl::value != 0 >:: alignment_of_type_can_not_be_zero_assert_failed check1; // if you are there means aligment of type passed can not be calculated or compiler can not handle this situation (sorry, nothing can be done there) }; // borland compilers seem to be unable to handle long double correctly, so this will do the trick: struct _long_double_wrapper{ long double value; }; } template <class _Tp> struct alignment_of: public detail::_alignment_of_impl<_Tp>::type {}; template <class _Tp> struct alignment_of<_Tp&>: public alignment_of<_Tp*> {}; template<> struct alignment_of<long double>: public alignment_of<detail::_long_double_wrapper> {}; 

Microsoft sekali lagi unggul di sini dengan Visual Studio mereka, yang bahkan memiliki built-in makro non-standar built -in makro, masih menghasilkan hasil yang salah ketika menggunakannya.

Penjelasan dari dorongan
Pengguna Visual C ++ harus mencatat bahwa MSVC memiliki beragam definisi "penyelarasan". Sebagai contoh, pertimbangkan kode berikut:

 typedef long long align_t; assert(boost::alignment_of<align_t>::value % 8 == 0); align_t a; assert(((std::uintptr_t)&a % 8) == 0); char c = 0; align_t a1; assert(((std::uintptr_t)&a1 % 8) == 0); 

Dalam kode ini, meskipun boost :: alignment_of <align_t> melaporkan bahwa align_t memiliki perataan 8-byte, pernyataan akhir akan gagal untuk build 32-bit karena a1 tidak disejajarkan pada batas 8 byte. Perhatikan bahwa jika kita menggunakan MSVC intrinsik __alignof di tempat boost :: alignment_of kita masih akan mendapatkan hasil yang sama. Bahkan untuk persyaratan penyelarasan MSVC (dan janji) hanya benar-benar berlaku untuk penyimpanan dinamis, dan bukan tumpukan.


Biarkan saya mengingatkan Anda apa yang harus dilakukan std :: alignment_of template - mengembalikan nilai yang mewakili persyaratan untuk penempatan elemen jenis ini di memori. Jika sedikit terganggu, unsur setiap jenis adalah apa yang lokasi memori, dan jika array adalah (hunian) terus menerus, maka, misalnya, kelas mungkin menjadi "lubang" antara unsur-unsur kelas anggota (struct kelas sizeof {Char a;} kemungkinan besar tidak akan sama dengan 1, meskipun ada 1 byte dari semua yang ada di dalamnya, karena kompiler akan menyelaraskannya ke 1 + 3 byte selama proses optimasi).

Sekarang mari kita lihat kodenya lagi. Kami mendeklarasikan struktur _alignment_of_trick , di mana kami menempatkan elemen tipe yang diperiksa dengan “indent” dalam memori 1 byte. Dan periksa perataan dengan hanya mengurangi ukuran jenis yang diperiksa dari ukuran struktur yang dihasilkan. Yaitu, jika kompiler memutuskan untuk "menempel" ruang kosong antara elemen dari tipe yang diperiksa dan karakter sebelumnya, maka kita mendapatkan nilai alignment tipe dalam struktur.

Juga di sini pernyataan statis pertama kali ditemui sebagai tipe. Mereka dinyatakan sebagai:

 namespace intern { // since we have no static_assert in pre-C++11 we just compile-time assert this way: struct type_traits_asserts { template<bool> struct make_signed_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert; template<bool> struct make_unsigned_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert; template<bool> struct not_allowed_arithmetic_type_assert; template<bool> struct alignment_of_type_can_not_be_zero_assert; }; template<> struct type_traits_asserts::make_signed_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert<true> { typedef bool make_signed_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert_failed; }; template<> struct type_traits_asserts::make_unsigned_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert<true> { typedef bool make_unsigned_template_require_that_type_shall_be_a_possibly_cv_qualified_but_integral_type_assert_failed; }; template<> struct type_traits_asserts::not_allowed_arithmetic_type_assert<true> { typedef bool not_allowed_arithmetic_type_assert_failed; }; template<> struct type_traits_asserts::alignment_of_type_can_not_be_zero_assert<true> { typedef bool alignment_of_type_can_not_be_zero_assert_failed; }; } 

Bahkan, template khusus ini diperlukan untuk mengganti static_assert dari C ++ 11, yang terletak di dalam definisi kelas. Penegasan seperti itu lebih ringan dan sangat terspesialisasi daripada penerapan STATIC_ASSERT secara umum dari bab 2 , dan memungkinkan Anda untuk tidak menyeret file header core.h ke dalam type_traits .

gambar Banyak pola? Akan ada lebih banyak! Kita akan membahas hal ini untuk saat ini, karena kisah yang menarik tentang menggabungkan pemrograman template dengan teknologi SFINAE, serta mengapa saya harus menulis generator kode kecil, akan berlanjut.

Terima kasih atas perhatian anda

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


All Articles