0Pricing
C++ Academy · درس

SFINAE

فعّل الدوال بصورة مشروطة

SFINAE درس مجاني في C++ Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C++ Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C++ Academy 4 دروس في المجموع.

ماذا تعني SFINAE؟

يرمز SFINAE إلى "Substitution Failure Is Not An Error"، أي «فشل الاستبدال ليس خطأً». عندما يستبدل المترجم معاملات القالب وتكون النتيجة غير صحيحة نحويًا، يُحذف المرشح ببساطة بدلًا من التسبب في خطأ حاد.

  • تتيح التحميل الزائد الشرطي.
  • تشكل أساس الاختيار في وقت الترجمة.
#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
isOdd(T n) { return n % 2 != 0; }

int main() {
    std::cout << std::boolalpha << isOdd(3) << '\n';
    return 0;
}

فشل الاستبدال عمليًا

إذا أدى استبدال نوع إلى جعل توقيع الدالة غير صالح، يتجاهله المترجم بهدوء ويتابع فحص المرشحين الآخرين.

#include <iostream>

template <typename T>
auto sizeOfValue(T t) -> decltype(t.size()) { return t.size(); }

int main() {
    std::string s = "hello";
    std::cout << sizeOfValue(s) << '\n';
    return 0;
}

تحميلان زائدان، أحدهما يفوز

وفر تحميلين زائدين بحيث ينجح الاستبدال في أحدهما فقط لنوع معين. تتخلص SFINAE من الآخر غير الصالح.

#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_integral<T>::value, const char*>::type
name() { return "integral"; }

template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, const char*>::type
name() { return "floating"; }

int main() {
    std::cout << name<int>() << ' ' << name<double>() << '\n';
    return 0;
}

decltype وSFINAE للتعابير

يتيح استخدام decltype على تعبير في نوع الإرجاع وجود الدالة فقط إذا كان ذلك التعبير صالحًا للنوع.

#include <iostream>

template <typename T>
auto tryDouble(T x) -> decltype(x + x) { return x + x; }

int main() {
    std::cout << tryDouble(21) << '\n';
    std::cout << tryDouble(2.5) << '\n';
    return 0;
}

اكتشاف عضو

تكتشف حيلة SFINAE التقليدية ما إذا كان النوع يحتوي على عضو معين، وذلك بالتحقق من إمكانية ترجمة تعبير يستخدمه.

#include <iostream>
#include <type_traits>
#include <vector>

template <typename T>
auto hasSize(int) -> decltype(std::declval<T>().size(), std::true_type{});

template <typename T>
std::false_type hasSize(...);

int main() {
    std::cout << std::boolalpha;
    std::cout << decltype(hasSize<std::vector<int>>(0))::value << '\n';
    std::cout << decltype(hasSize<int>(0))::value << '\n';
    return 0;
}

البديل ...

يأتي التحميل الزائد الذي يأخذ ... (معاملات متغيرة) بأدنى أولوية. ويعمل كخيار شامل عندما يفشل التحميل الزائد المقيّد بـ SFINAE.

#include <iostream>

template <typename T>
auto pick(int) -> decltype(T{}.value, void()) { std::cout << "has value\n"; }

template <typename T>
void pick(...) { std::cout << "no value\n"; }

struct WithValue { int value = 0; };

int main() {
    pick<WithValue>(0);
    pick<int>(0);
    return 0;
}

void_t

تبسّط std::void_t في C++17 عملية الاكتشاف؛ فهي تحوّل أي أنواع صحيحة نحويًا إلى void، ولذلك لا يُفعّل التخصيص إلا عندما يكون التعبير الذي يجري التحقق منه صالحًا.

#include <iostream>
#include <type_traits>
#include <vector>

template <typename T, typename = void>
struct HasSize : std::false_type {};

template <typename T>
struct HasSize<T, std::void_t<decltype(std::declval<T>().size())>> : std::true_type {};

int main() {
    std::cout << std::boolalpha;
    std::cout << HasSize<std::vector<int>>::value << '\n';
    std::cout << HasSize<int>::value << '\n';
    return 0;
}

لماذا لا نكتفي بحدوث الخطأ؟

من دون SFINAE، سيؤدي الاستبدال غير الصالح إلى خطأ ترجمة حاد لا يمكن التعافي منه. وتحوله SFINAE إلى عبارة هادئة تعني «ليس مرشحًا».

#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_pointer<T>::value, bool>::type
isNull(T p) { return p == nullptr; }

int main() {
    int x = 0;
    std::cout << std::boolalpha << isNull(&x) << '\n';
    return 0;
}

بديل توزيع الوسوم

قد يصعب قراءة SFINAE. يختار توزيع الوسوم تنفيذًا باستخدام أنواع وسم صغيرة وحل التحميل الزائد بدلًا منها.

#include <iostream>
#include <type_traits>

template <typename T>
void impl(T v, std::true_type) { std::cout << "integral: " << v << '\n'; }

template <typename T>
void impl(T v, std::false_type) { std::cout << "other: " << v << '\n'; }

template <typename T>
void handle(T v) { impl(v, std::is_integral<T>{}); }

int main() {
    handle(5);
    handle(2.5);
    return 0;
}

SFINAE وconstexpr if

في C++17، غالبًا ما يستبدل if constexpr استخدام SFINAE للتفرع داخل دالة واحدة، إذ تُحذف الفروع غير المستخدمة في وقت الترجمة.

#include <iostream>
#include <type_traits>

template <typename T>
void describe(T v) {
    if constexpr (std::is_integral<T>::value) {
        std::cout << "integral: " << v << '\n';
    } else {
        std::cout << "other: " << v << '\n';
    }
}

int main() {
    describe(7);
    describe(3.14);
    return 0;
}

جمع الأجزاء

تتألق SFINAE عندما تحتاج إلى الاختيار بين قوالب دوال منفصلة استنادًا إلى خصائص النوع على مستوى التحميل الزائد.

#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_signed<T>::value, T>::type
absValue(T v) { return v < 0 ? -v : v; }

int main() {
    std::cout << absValue(-9) << '\n';
    return 0;
}

تحقق سريع

اختبر مدى فهمك لـ SFINAE.

مراجعة

لقد تعلمت SFINAE:

  • يزيل الاستبدال غير الصالح المرشح بدلًا من التسبب في خطأ
  • تُستخدم مع enable_if وdecltype وvoid_t لتمكين الدوال بصورة شرطية
  • يعمل التحميل الزائد ... كخيار بديل
  • تشمل البدائل الحديثة توزيع الوسوم وif constexpr

بعد ذلك، ستركز بالتفصيل على أنماط enable_if التقليدية.

الأسئلة الشائعة

هل درس «SFINAE» مجاني؟

نعم — نص درس «SFINAE» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C++ Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C++ Academy 4 دروس في المجموع.

ماذا ستتعلم في «SFINAE»؟

فعّل الدوال بصورة مشروطة تتمرن على C++ Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ C++ Academy؟

لا تُشترط خبرة سابقة. C++ Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «SFINAE»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس C++ Academy هذا؟

نعم. كل درس في C++ Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. التخصيص الكامل
  2. التخصيص الجزئي
  3. SFINAE
  4. أنماط enable_if
← العودة إلى C++ Academy