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

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

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


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

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

لقد حان الوقت لنوع type_traits وكل هذا "سحر القالب الخاص".

رابط إلى 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 ++ قالب سحري


بعد الانتهاء من الكلمات الأساسية لـ C ++ 11 وجميع "المفاتيح" المعتمدة على التعريف بين عمليات التنفيذ ، بدأت في ملء type_traits . في الحقيقة ، كان لدي بالفعل عدد قليل جدًا من فئات القوالب ، على غرار الفئات القياسية ، التي عملت بالفعل في المشاريع لفترة طويلة ، وبالتالي بقي إدخال كل هذا في نفس النموذج ، بالإضافة إلى إضافة الوظائف المفقودة.

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

4.1 البداية صغيرة


لقد بدأت بقوالب بسيطة لـ std :: Integral_constant و std :: bool_constant وقوالب صغيرة مماثلة.

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

بناءً على الشروط الشرطية ، يمكنك إدخال نماذج مناسبة للعمليات المنطقية {"و" أو "أو" ، "لا"} عبر الأنواع (وتعتبر جميع هذه العمليات مناسبة في مرحلة التجميع! إنها رائعة ، أليس كذلك؟):

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

هناك ثلاث نقاط تستحق الاهتمام هنا:

1) من المهم دائمًا وضع مسافة بين أقواس الزاوية ('<' و '>') من القوالب ، لأنه قبل C ++ 11 لم يكن هناك توضيح في المعيار حول كيفية تفسير ">>" و "<<" في التعليمات البرمجية مثل _or _ <_ B2 ، _or _ <_ B3، _B4 >> ، وبالتالي تعامل جميع المترجمين تقريبًا مع هذا كعامل تبديل بت ، مما يؤدي إلى خطأ في الترجمة .

2) في بعض المترجمين (Visual Studio 6.0 على سبيل المثال) كان هناك خطأ يتألف من حقيقة أنه كان من المستحيل استخدام نوع الفراغ كمعلمة قالب. لهذه الأغراض ، يتم إدخال نوع void_type منفصل في المقطع أعلاه لاستبدال نوع الفراغ حيث تكون قيمة معلمة القالب الافتراضية مطلوبة.

3) المترجمون القدامى جدًا (Borland C ++ Builder على سبيل المثال) لديهم نوع منطقي تم تنفيذه بشكل مخادع ، والذي تحول في بعض الحالات "فجأة" إلى int ( صحيح -> 1 ، خطأ -> 0) ، بالإضافة إلى أنواع المتغيرات الثابتة الثابتة من النوع منطقية (وليس فقط) ، إذا كانت مضمنة في فئات القالب. نتيجة لكل هذه الفوضى ، نتيجة لذلك ، لمقارنة غير ضارة تمامًا في أسلوب my_template_type :: static_bool_value == false ، يمكن للمترجم بسهولة إصدار ساحر لا يمكنه إرسال "نوع غير محدد" إلى int (0) أو شيء من هذا القبيل. لذلك ، من الضروري دائمًا محاولة الإشارة بوضوح إلى نوع القيم للمقارنة ، وبالتالي مساعدة المترجم على تحديد الأنواع التي يتعامل معها.


أضف المزيد من العمل بقيم ثابتة ومتغيرة . أولاً ، الإزالة التي تم تنفيذها بشكل تافه ... حيث نقوم ببساطة بتخصيص القالب لمعدلات نوع معينة - إذا كان النوع الذي يحتوي على التعديل في القالب ، يجب على المترجم ، بعد النظر في جميع التخصصات (تذكر مبدأ SFINAE من الفصل السابق ) من القالب ، حدد الأنسب (مع إشارة صريحة للمعدل المطلوب) :

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

ثم نقوم بتطبيق قوالب add_ ... حيث يكون كل شيء أكثر تعقيدًا بالفعل:

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

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

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

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

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


All Articles