كيف كتبت مكتبة C ++ 11 القياسية أو لماذا يكون التعزيز مخيفًا جدًا. الفصل 4.3

نواصل المغامرة.

ملخص الأجزاء السابقة


نظرًا للقيود المفروضة على القدرة على استخدام برامج التحويل البرمجي لـ C ++ 11 ، وبسبب عدم وجود بدائل ، أراد التعزيز كتابة تطبيقه الخاص لمكتبة C ++ 11 القياسية أعلى مكتبة C ++ 98 / C ++ 03 المزودة مع برنامج التحويل البرمجي.

تم تنفيذ Static_assert ، noexcept ، countof ، وأيضًا بعد النظر في جميع التعريفات غير القياسية وميزات المترجم ، ظهرت معلومات حول الوظيفة التي يدعمها المترجم الحالي. يتم تضمين تطبيقه الخاص لـ nullptr ، والذي يتم اختياره في مرحلة التجميع.

لقد حان الوقت لنوع type_traits وكل هذا "سحر القالب الخاص". في الأجزاء السابقة من هذا الفصل ، درسنا تنفيذي للقوالب الأساسية للمكتبة القياسية ، وفي هذا الجزء سنتحدث عن دمج تقنية SFINAE مع القوالب وقليلاً عن إنشاء التعليمات البرمجية.

رابط إلى GitHub بالنتيجة لهذا اليوم للصبر وغير القراء:
نرحب بالالتزامات والنقد البناء
المزيد من قوالب C ++ تحت القط.

جدول المحتويات


مقدمة
الفصل 1. فيام الإشراف على vadens
الفصل 2. #ifndef __CPP11_SUPPORT__ # تعريف __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
الفصل 3. إيجاد تنفيذ nullptr الكمال
الفصل 4. C ++ قالب سحري
.... 4.1 نبدأ صغيرة
.... 4.2 حول عدد الأخطاء المعجزة التي يجمعها لنا السجل
.... 4.3 المؤشرات والجميع
.... 4.4 ما هو مطلوب أيضًا لمكتبة النماذج
الفصل الخامس
...

الفصل 4. قالب "سحر" C ++. استمرار


4.3 المؤشرات والجميع للجميع


في هذه المرحلة ، كان بإمكاني فقط الحصول على معلومات حول ما إذا كان النوع مصفوفة لـ std :: is_array وكان من الممكن بدء قوالب للمؤشرات. كان التنفيذ تافهاً أيضًا ، ولكن ليس بدون افتراضات.

// 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 { }; */ 

تخصص قالب بسيط للصفائف ذات طول معين "يمسك" جميع أنواع المصفوفات ، ومع ذلك ، تنشأ المشكلة مع النوع غير المكتمل T [] (صفيف بدون تحديد الطول). والحقيقة هي أن هذا النوع لم يتم تعريفه من قبل بعض المترجمين (C ++ Builder) عند تخصيص قالب ، ولم أجد حتى الآن حلاً شاملاً هنا.

بعد "تعليم" المكتبة لتعريف الأنواع المضمنة ، والمحاذاة في ذاكرة الكتابة ، والعمل مع مُعدِّلات النوع والأشياء الأساسية الأخرى من خلال القوالب في وقت الترجمة ، حان الوقت للمؤشرات والمراجع.

الصورة في C ++ ، يمكن تمييز مجموعتين من المؤشرات - مؤشرات لأعضاء الصف ومؤشرات لكائنات أخرى. ما سبب أهمية هذا الفصل لمواصلة تنفيذ المكتبة القياسية؟ الحقيقة هي أن المؤشرات لأعضاء الصف لها فرق كبير عن المؤشرات الأخرى من خلال وجود هذا ، أي مؤشر إلى كائن من هذه الفئة. وبشكل قياسي ، تحتوي المؤشرات إلى عضو في الفصل على بنية منفصلة للتحديد ، وهي نوع منفصل ، ولا يمكن تمثيلها من خلال مؤشر عادي. من الناحية العملية ، يُترجم هذا إلى حقيقة أن حجم المؤشر لعضو الفصل عادةً ما يكون أكبر من حجم المؤشر العادي (الذي == sizeof (void *) ) ، لأن لتنفيذ وظائف العضو الظاهري للفئة ، وكذلك لتخزين هذا المؤشر ، عادة ما يقوم المترجمون بتنفيذ مؤشرات لعضو الفئة كهيكل (اقرأ عن الوظائف والهيكل الظاهري). يتم ترك الطريقة لتقديم المؤشرات لأعضاء الفصل ، وفقًا للمعيار ، وفقًا لتقدير المترجم ، ولكننا سنتذكر هذا الاختلاف في الحجم والعرض التقديمي عند النظر في المزيد من التعليمات البرمجية.

لتحديد مؤشر منتظم لكائن ما ، سنكتب قالب is_pointer بسيط ، بالإضافة إلى قالب is_lvalue_reference لمراجع الكائن (نترك is_value_reference ، لأنه حتى المعيار الحادي عشر لم يكن هناك عامل تشغيل && بالإضافة إلى دلالات الانتقال بأكملها):

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

لم يعد هناك أي شيء جديد في الأساس هنا ، فقد تم عمل نفس الشيء في الأجزاء السابقة من هذا الفصل. دعونا نستمر في تحديد مؤشرات للكائنات - الآن دعونا نلقي نظرة على مؤشرات الوظائف.
من المهم أن نفهم أن دالة ووظيفة عضو لفئة هي كيانات مختلفة تمامًا وفقًا للمعيار:

  • سيكون المؤشر الأول عاديًا (مؤشر إلى كائن) ، والثاني سيحتوي على مؤشر لعضو الفئة.

 void (*func_ptr)(int); //  'func_ptr'    'void func(int){}' void (ClassType::*mem_func_ptr)(int); //  'mem_func_ptr'  -  'ClassType'  'void ClassType::func(int){}' 

  • يمكنك إنشاء ارتباط إلى الأول (ارتباط الكائن) ، ولكن لا يمكنك إنشاء ارتباط ثانٍ.

 void (&func_ref)(int); //  'func_ref'    'void func(int){}' //-------------------- //   -     
هنا سأذكر فقط القليل عن إنشاء التعليمات البرمجية. نظرًا لأنه قبل C ++ 11 لم تكن هناك نماذج ذات عدد متغير من المعلمات ، تم تحديد جميع القوالب التي يمكن أن يكون فيها عدد مختلف من المعلمات من خلال التخصص في القالب الرئيسي مع أي عدد كبير من المعلمات عند الإدخال وتهيئتها بواسطة معلمات وهمية افتراضية. الشيء نفسه ينطبق على وظيفة الزائد ، مثل لم يكن هناك وحدات ماكرو مع عدد متغير من المعلمات أيضًا. منذ كتابة 60-70 سطرًا من نفس النوع من تخصصات القالب بيديك ، فإن التحميل الزائد للوظيفة هو مهمة مملة وغير مجدية إلى حد ما ، وهو محفوف أيضًا بإمكانية ارتكاب خطأ. لقد كتبت مولدًا بسيطًا لرمز القالب والوظائف الزائدة لهذه الأغراض. قررت أن أقصر نفسي على تحديد الوظائف إلى 24 معلمة وهذا يبدو مرهقًا إلى حد ما في الرمز ، ولكنه بسيط وواضح:

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

نحدد الأنواع المألوفة لنا من الفصل السابق لتقنية SFINAE:

 namespace detail { // SFINAE magic typedef char _yes_type; struct _no_type { char padding[8]; }; } 

بعض وحدات الماكرو لمزيد من الراحة
 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 } 


يتم تعريف وحدات الماكرو بحيث يكون من الملائم نسبيًا إعادة تعريف TYPES و ARGS كقائمة للأنواع والمعلمات ، ثم استبدال الماكرو _IS_MEM_FUN_PTR لإنشاء تعريفات لجميع أنواع الوظائف الممكنة بواسطة المعالج المسبق. من الجدير أيضًا الانتباه إلى حقيقة أنه بالنسبة لمجمعي Microsoft ، فإن اتفاقيات الاتصال ( __fastcall و __stdcall و __cdecl ) مهمة أيضًا ، لأن مع اصطلاحات مختلفة ، ستكون الدالات مختلفة ، على الرغم من أنها تحتوي على نفس مجموعة الحجج والقيمة المرجعة. ونتيجة لذلك ، يتم استخدام هذا التصميم الكلي الفخم بالكامل بشكل مضغوط تمامًا:

 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 } 

والآن على كل ما كتب:

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

للتحقق مما إذا كان النوع هو وظيفة عضو لفئة ، يتم التحقق أولاً مما إذا كان النوع مرجعًا. ثم يتم إنشاء مؤشر من هذا النوع واستبداله في وظيفة التحقيق. باستخدام تقنية SFINAE ، يحدد المترجم الحمل الزائد اللازم لوظائف المسبار لمثل هذا المؤشر ، وبناءً على نتيجة المقارنة مع _yes_type ، يشكل النتيجة.

استنادًا إلى التحقق من وظيفة عضو في الفصل الدراسي ، تتم كتابة اختبار نوع حول ما إذا كان ينتمي إلى نوع الوظيفة. نتحقق مما إذا كان النوع مرجعًا ، إذا لم يكن كذلك ، فإننا نبحث عن تخصص مناسب لهياكل مسبار القالب لمؤشر من هذا النوع ، والذي سيكون true_type لأي مؤشرات دالة مع ما يصل إلى 24 معلمة.

والآن نستخدم النتيجة لتنفيذ وظيفة is_ . هنا ، وللسبب نفسه كما في الجزء السابق ، لم أتمكن من وراثة هذه البنية من Integral_constant ، لذلك "محاكاة" Integral_constant.

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

ولتطبيق is_member_function_pointer ، لا يزال الأمر أبسط:

 // 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 { }; 

علاوة على ذلك ، بناءً على هذه الأنماط ، يمكننا تحديد ما إذا كان النوع عضوًا في الفصل من حيث المبدأ:

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

العمليات المنطقية 'و' أو 'أو' و 'not' المستخدمة على أنواع من الجزء الأول
 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); } }; } 


هنا نستخدم العمليات المنطقية على الأنواع التي ، بمساعدة القالب الشرطي ، تختار في النهاية نوع القالب المناسب. برمجة القوالب بكل مجده ، ونتيجة لذلك ، في مرحلة التجميع ، لدينا بالفعل معلومات حول ما إذا كان النوع عضوًا في الفصل. تماما "غاضب" ، ولكن كيف مذهلة وفعالية!

برمجة قالب أكثر نقاءً على نفس العناصر المنطقية ولدينا أساسيات ، is_compound ، إلخ. علامات (هذا يسعدني ، لكنك؟):

 // 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 { }; 
سيلاحظ القارئ اليقظ تعليق تعريف is_enum . الحقيقة هي أنني لم أجد طرقًا لتمييز التعداد من الأنواع الأخرى ، ولكن أعتقد أن هذا ممكن دون استخدام وحدات الماكرو المعتمدة على المترجم. ربما سيخبرك القارئ اليقظ والمعروف بطريقتك أو قطار الفكر في هذا الصدد.
لتحديد حقيقة أن النوع هو فئة ، ليس هناك حاجة إلى المزيد الآن:

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

وكل شيء سيكون على ما يرام ، لكن الاتحاد في C ++ لا يمكن تمييزه عن فئة في الحالة العامة. لأنها متشابهة جدًا في "مظاهرها الخارجية" ، ولم أستطع التحقق من الاختلافات (على سبيل المثال ، عدم القدرة على الوراثة من الاتحاد ) دون أخطاء في الترجمة. ربما سيقول لك أحدهم مناورة صعبة لتحديد الاتحاد عند التجميع ، ثم is_class تتوافق تمامًا مع المعيار.

في الجزء الأخير من هذا الفصل ، سأتحدث عن كيفية تنفيذ std :: decay و std :: common_type ، وكذلك ما الذي يجب إضافته إلى type_traits .

شكرا لكم على اهتمامكم.

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


All Articles