لا يُعدّ فشل الاستبدال خطأً.
مبدأ "فشل الاستبدال ليس خطأً" ( SFINAE ) هو مبدأ في لغة C++ ينص على أن الاستبدال غير الصحيح لمعاملات القوالب لا يُعد خطأً في حد ذاته. وقد استخدم ديفيد فانديفورد اختصار SFINAE لأول مرة لوصف تقنيات البرمجة ذات الصلة. [ 1 ]
على وجه التحديد، عند إنشاء مجموعة مرشحة لحلّ التحميل الزائد ، قد يكون بعض (أو كل) المرشحين في تلك المجموعة ناتجًا عن قوالب مُنشأة مع استبدال وسائط القالب (المستنتجة على الأرجح) بمعاملات القالب المقابلة. إذا حدث خطأ أثناء استبدال مجموعة من الوسائط لأي قالب مُعطى، يقوم المُصرّف بإزالة التحميل الزائد المُحتمل من مجموعة المرشحين بدلًا من التوقف مع ظهور خطأ في التجميع، شريطة أن يسمح معيار C++ بتجاهل خطأ الاستبدال هذا كما ذُكر. [ 2 ] إذا بقي مرشح واحد أو أكثر ونجح حلّ التحميل الزائد، يكون الاستدعاء سليمًا.
تم استبدال SFINAE إلى حد كبير بإضافة مفاهيم في C++20 ، والتي تسمح بالتعبير بشكل أنظف عن قيود نوع القالب.
مثال
يوضح المثال التالي حالة أساسية من حالات SFINAE:
struct Test { using Foo = int ; };// التعريف رقم 1 قالب < نوع T > دالة f ( نوع T :: Foo ) { // ... }// التعريف رقم 2 قالب < نوع الاسم T > دالة f ( T ) { // ... }int main () { f < Test > ( 10 ); // الاستدعاء رقم 1. f < int > ( 10 ); // الاستدعاء رقم 2. بدون خطأ (على الرغم من عدم وجود int::Foo) بسبب SFINAE. return 0 ; }هنا، محاولة استخدام نوع غير فئة في اسم مؤهل ( T::Foo) تؤدي إلى فشل الاستنتاج لأن f<int>ليس intلديه نوع متداخل باسم Foo، ولكن البرنامج سليم التكوين لأن دالة صالحة تبقى في مجموعة الدوال المرشحة.
على الرغم من أن SFINAE طُرح في البداية لتجنب إنشاء برامج غير سليمة عند ظهور تعريفات قوالب غير ذات صلة (مثلًا، من خلال تضمين ملف رأس)، فقد وجد العديد من المطورين لاحقًا أن هذا السلوك مفيدٌ للاستبطان أثناء الترجمة. وبالتحديد، يسمح للقالب بتحديد خصائص معينة لوسائط القالب عند إنشاء مثيل له.
على سبيل المثال، يمكن استخدام SFINAE لتحديد ما إذا كان نوع ما يحتوي على تعريف نوع معين:
استيراد std ؛template < typename T > struct HasTypedefFoobar { // يُضمن أن يكون للنوعين "Yes" و"No" أحجام مختلفة، // تحديدًا sizeof(Yes) == 1 و sizeof(No) == 2. using Yes = char [ 1 ]; using No = char [ 2 ];template < typename C > static Yes & test ( typename C :: Foobar * );template < typename > static No & test (...);// إذا كان حجم نتيجة استدعاء الدالة test<T>(nullptr) يساوي حجم (Yes)، فإن التحميل الزائد الأول قد نجح، وT له نوع متداخل يُسمى Foobar. static constexpr bool value = ( sizeof ( test < T > ( nullptr )) == sizeof ( Yes )); };struct Foo { using Foobar = float ; };int main () { std :: println ( "{}" , HasTypedefFoobar < int >:: value ); std :: println ( "{}" , HasTypedefFoobar < Foo >:: value ); return 0 ; }عند تعريف Tالنوع المتداخل Foobar، ينجح إنشاء الكائن الأول testويتم تمرير ثابت المؤشر الفارغ بنجاح. (ويكون نوع التعبير الناتج هو Yes.) إذا لم ينجح ذلك، فإن الدالة الوحيدة المتاحة هي الدالة الثانية test، ويكون نوع التعبير الناتج هو No. يُستخدم رمز الحذف ليس فقط لأنه يقبل أي وسيط، بل أيضًا لأن رتبة تحويله هي الأدنى، لذا يُفضّل استدعاء الدالة الأولى إن أمكن؛ وهذا يزيل أي لبس.
تبسيط لغة C++11
في لغة C++11 ، يمكن تبسيط الكود أعلاه إلى:
استيراد std ؛template < typename T , typename = void > struct HasTypedefFoobar : std :: false_type {};template < typename T > struct HasTypedefFoobar < T , std :: void_t < typename T :: Foobar >> : std :: true_type {};struct Foo { using Foobar = float ; };int main () { std :: println ( "{}" , HasTypedefFoobar < int >:: value ); std :: println ( "{}" , HasTypedefFoobar < Foo >:: value ); return 0 ; }مع توحيد أسلوب الكشف في اقتراح المكتبة الأساسية الإصدار 2 (n4562) ، يمكن إعادة كتابة الكود أعلاه على النحو التالي:
استيراد std ؛template < typename T > using HasTypedefFoobarUnderlying = typename T :: Foobar ;struct Foo { using Foobar = float ; };int main () { std :: println ( "{}" , std :: is_detected < HasTypedefFoobarUnderlying , int >:: value ); std :: println ( "{}" , std :: is_detected < HasTypedefFoobarUnderlying , Foo >:: value ); return 0 ; }استخدم مطورو Boost تقنية SFINAE في boost::enable_if [ 3 ] وبطرق أخرى.
مراجع
- ^ فانديفورد ، ديفيد. نيكولاي م. جوسوتيس (2002). قوالب C++: الدليل الكامل أديسون ويسلي بروفيشنال. رقم ISBN 0-201-73484-2.
- ↑ المنظمة الدولية للتوحيد القياسي. "ISO/IEC 14882:2003، لغات البرمجة - C++"، § 14.8.2.
- ↑ تفعيل التعزيز إذا
- لغة سي++
- أنماط تصميم البرمجيات
