ما هو بالضبط "السياق المباشر" المذكور في معيار C++ 11 الذي تنطبق عليه SFINAE؟




templates c++11 (2)

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

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

لذلك أعطى نموذج وظيفة مثل هذا:

template<typename T>
void
func(typename T::type* arg);

و "الرجوع" الذي سيتم استخدامه في حالة فشل الخصم للوظيفة الأخرى:

template<typename>
void
func(...);

و قالب الفصل مثل:

template<typename T>
  struct A
  {
    typedef T* type;
  };

سوف يستبدل الاستدعاء إلى func<A<int&>>(nullptr) A<int&> لـ T وللتحقق مما إذا كان T::type موجودًا أم لا ، يجب أن يقوم بإنشاء A<int&> . إذا تخيلنا وضع نص صريح قبل بدء الاتصال بـ func<A<int&>(nullptr) :

template class A<int&>;

ثم قد يفشل ذلك ، لأنه يحاول إنشاء نوع int&* ولا يسمح بالمؤشرات إلى المراجع. لا نصل إلى نقطة التحقق مما إذا كان الاستبدال ناجحًا ، نظرًا لوجود خطأ فادح في تأجيل A<int&> .

لنفترض الآن أن هناك تخصصًا واضحًا لـ A :

template<>
  struct A<char>
  {
  };

يتطلب استدعاء func<A<char>>(nullptr) إنشاء مثيل لـ A<char> ، لذا تخيل إنشاء مثيل واضح في مكان ما في البرنامج قبل المكالمة:

template class A<char>;

هذا التأليف هو موافق ، وليس هناك خطأ من هذا ، لذلك ننتقل إلى استبدال الوسيطة. لقد نجحت عملية إنشاء A<char> ، ولكن نوع A<char>::type غير موجود ، ولكن هذا موافق لأنه مشار إليه فقط في إعلان الدالة func ، لذلك يؤدي فقط إلى فشل خصم الحجة ، والظهور ... يتم استدعاء الوظيفة بدلاً من ذلك.

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

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

تحدد الفقرة 14.8.2 / 8 من معيار C ++ 11 الشروط التي بموجبها يؤدي فشل الاستبدال أو لا ينتج عنه خطأ تجميع "صعب" (مما يؤدي إلى فشل التجميع) أو في خطأ "ناعم" والذي تتسبب في تجاهل المحول البرمجي لقالب من مجموعة من المرشحين لدقة الحمولة الزائدة (بدون تفشل الترجمة وتمكين لغة SFINAE المعروفة):

إذا أدى استبدال في نوع أو تعبير غير صحيح ، فاخفق في عملية الخصم. نوع أو تعبير غير صحيح هو نوع سيبدو غير صحيح إذا تمت كتابته باستخدام الوسيطات البديلة. [ملاحظة: يتم إجراء فحص الوصول كجزء من عملية الاستبدال. —end note] فقط الأنواع والتعبيرات غير الصالحة في السياق المباشر لنوع الوظيفة وأنواع المعلمات الخاصة بها يمكن أن تؤدي إلى فشل في الاستنتاج . [...]

تظهر الكلمات " السياق المباشر " 8 مرات فقط في C ++ 11 Standard بالكامل ، وفي كل مرة يتم متابعتها بواسطة (أو تحدث كجزء من) مثيل من النص التالي (غير المعياري):

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

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

سؤال:

هل يمكن أن تقدم تفسيراً وإجراء قرار و / أو بعض الأمثلة الملموسة للمساعدة في معرفة الحالات التي يحدث فيها خطأ في الاستبدال ولا يحدث في " السياق المباشر " لنوع الوظيفة وأنواع معلمات قالبها؟


السياق المباشر هو أساسا ما تراه في إعلان القالب نفسه. كل شيء خارج ذلك هو خطأ صعب. أمثلة للخطأ الصعب:

#include <type_traits>

template<class T>
struct trait{ using type = typename T::type; };

template<class T, class U = typename trait<T>::type>
void f(int);
void f(...);

template<class T, class U = typename T::type>
void g(int);
void g(...);

template<class>
struct dependent_false : std::false_type{};

template<class T>
struct X{
    static_assert(dependent_false<T>(), "...");
    using type = void;
};

int main(){
    f<int>(0);
    g<X<int>>(0);
}

نسخة حية.





sfinae