C++: مقدمة في ما وراء برمجة القوالب

آخر تحديث: 2026-08-26

في الدرس 46 تعلمنا عن المؤشرات الذكية المتقدمة.

الآن، سنتطرق إلى "السحر الأسود" في C++ - ما وراء برمجة القوالب.

ما وراء برمجة القوالب هي تقنية تُنفذ الكود في وقت الترجمة، مما يمكن أن يحسن أداء وقت التنفيذ بشكل كبير.


1. نظرة عامة على ما وراء برمجة القوالب

(1) 1.1 ما هي ما وراء برمجة القوالب؟

ما وراء برمجة القوالب (TMP) هي تقنية تستخدم القوالب لإجراء حسابات في وقت الترجمة.

الخصائص:


(2) 1.2 لماذا نستخدم ما وراء برمجة القوالب؟

الميزة الوصف
الأداء حسابات وقت الترجمة، أسرع في وقت التنفيذ
أمان الأنواع فحص الأنواع في وقت الترجمة
التعميم كتابة كود عام حقاً


2. الحسابات في وقت الترجمة

(1) 2.1 المضروب في وقت الترجمة

مثال: حساب المضروب باستخدام القوالب (الصعوبة ⭐⭐⭐)

▶ مثال 1: مثال برمجي (الصعوبة ⭐)

TEXT 📖 للعرض فقط
#include <iostream>

// القالب الأساسي
template<int N>
struct Factorial {
 static const int value = N * FactorialN-1::value;
};

// التخصيص: شرط الإنهاء
template<>
struct Factorial<0> {
 static const int value = 1;
};

int main() {
 std::cout << "5! = " << Factorial<5>::value << std::endl; // 120
 // النتيجة حُسبت في وقت الترجمة، تُستخدم مباشرة في وقت التنفيذ
 
 return 0;
}

الخرج:

TEXT 📖 للعرض فقط
5! = 

▶ مثال 2: مثال برمجي (الصعوبة ⭐)

💡 نصيحة:


(2) 2.2 دوال constexpr (C++11، موصى بها)

أدخلت C++11 constexpr، مما جعل حسابات وقت الترجمة أبسط.

مثال: حساب المضروب باستخدام constexpr (الصعوبة ⭐⭐)

CPP
#include <iostream>

constexpr int factorial(int n) {
 return n <= 1 ? 1 : n * factorial(n - 1);
}

int main() {
 constexpr int result = factorial(5); // حساب وقت الترجمة
 std::cout << "5! = " << result << std::endl; // 120
 
 return 0;
}

الخرج:

TEXT 📖 للعرض فقط
5! = 

💡 نصيحة:



3. سمات الأنواع

(1) 3.1 ما هي سمات الأنواع؟

سمات الأنواع هي تقنيات للاستعلام عن معلومات الأنواع أو تعديلها في وقت الترجمة.

مثال: استخدام type_traits للتحقق من الأنواع (الصعوبة ⭐⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <type_traits>

int main() {
 std::cout << std::is_integralint::value << std::endl; // 1 (true)
 std::cout << std::is_integraldouble::value << std::endl; // 0 (false)
 std::cout << std::is_pointer<int*>::value << std::endl; // 1 (true)
 
 return 0;
}

(2) 3.2 سمات أنواع مخصصة

مثال: اكتشاف ما إذا كانت الفئة تمتلك دالة عضو (الصعوبة ⭐⭐⭐⭐)

CPP
#include <iostream>
#include <type_traits>

// اكتشاف ما إذا كانت هناك دالة عضو serialize
template<typename T>
struct has_serialize {
private:
 template<typename U>
 static auto test(int) -> decltype(std::declvalU().serialize(), std::true_type{});
 
 template<typename U>
 static std::false_type test(...);
 
public:
 static const bool value = decltype(testT(0))::value;
};

struct Person {
 void serialize() {}
};

int main() {
 std::cout << has_serializePerson::value << std::endl; // 1 (true)
 std::cout << has_serializeint::value << std::endl; // 0 (false)
 
 return 0;
}


4. SFINAE

(1) 4.1 ما هو SFINAE؟

SFINAE (Substitution Failure Is Not An Error): عندما يفشل استبدال معامل القالب، ليس خطأً - سيجرب المترجم التحميلات الزائدة الأخرى.

الغرض: اختيار تحميلات دوال مختلفة بناءً على خصائص النوع.


(2) 4.2 مثال: SFINAE لاختيار التحميلات الزائدة (الصعوبة ⭐⭐⭐⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <type_traits>
#include <string>

// الإصدار 1: للأنواع الصحيحة
template<typename T>
typename std::enable_if<std::is_integralT::value, std::string>::type
toString(T value) {
 return std::to_string(value) + " (integer)";
}

// الإصدار 2: للأنواع الأخرى
template<typename T>
typename std::enable_if<!std::is_integralT::value, std::string>::type
toString(T value) {
 return "Not an integer";
}

int main() {
 std::cout << toString(42) << std::endl; // 42 (integer)
 std::cout << toString(3.14) << std::endl; // Not an integer
 
 return 0;
}

الخرج:

TEXT 📖 للعرض فقط
(program output)


5. القوالب متعددة المتغيرات

(1) 5.1 ما هي القوالب متعددة المتغيرات؟

القوالب متعددة المتغيرات تسمح للقوالب بقبول عدد تعسفي من المعاملات.

مثال: طباعة أي عدد من المعاملات (الصعوبة ⭐⭐⭐)

CPP
#include <iostream>

// دالة إنهاء التكرار
void print() {
 std::cout << std::endl;
}

// طباعة تكرارية
template<typename T, typename... Args>
void print(T first, Args... rest) {
 std::cout << first << " ";
 print(rest...); // استدعاء تكراري
}

int main() {
 print(1, 2.5, "hello", 'a');
 // الخرج: 1 2.5 hello a
 
 return 0;
}


6. تعبيرات الطي في C++17

(1) 6.1 الاستخدام الأساسي

أدخلت C++17 تعبيرات الطي، التي تُبسط القوالب متعددة المتغيرات.

مثال: جمع (الصعوبة ⭐⭐)

TEXT 📖 للعرض فقط
#include <iostream>

template<typename... Args>
auto sum(Args... args) {
 return (args + ...); // تعبير طي
}

int main() {
 std::cout << sum(1, 2, 3, 4, 5) << std::endl; // 15
 return 0;
}

▶ مثال 3: حساب constexpr في وقت الترجمة (الصعوبة ⭐)

CPP
#include <iostream>

// دوال constexpr تُقيَّم في وقت الترجمة
constexpr int square(int n) {
    return n * n;
}

constexpr int cube(int n) {
    return n * n * n;
}

int main() {
    // حساب وقت الترجمة، النتائج مُضمَّنة مباشرة
    constexpr int sq = square(5);
    constexpr int cb = cube(3);

    std::cout << "5^2 = " << sq << std::endl;
    std::cout << "3^3 = " << cb << std::endl;

    // تأكيدات ثابتة تتحقق من تقييم وقت الترجمة
    static_assert(sq == 25, "square(5) should be 25");
    static_assert(cb == 27, "cube(3) should be 27");

    return 0;
}
▶ جرّب الكود

الخرج:

TEXT 📖 للعرض فقط
5^2 = 
3^3 = 

❓ أسئلة شائعة

س: هل ما وراء برمجة القوالب صعبة التعلم؟ ج: نعم. يُنصح بإتقان القوالب الأساسية أولاً قبل تعلم ما وراء البرمجة. معظم المشاريع لا تحتاج ما وراء البرمجة.


س متى أستخدم ما وراء برمجة القوالب؟
ج - كتابة مكتبات (مثل STL) - عندما تحتاج أداءً أقصى - عندما تحتاج فحص أنواع في وقت الترجمة

س هل يمكن لـ constexpr أن تحل محل ما وراء برمجة القوالب؟
ج جزئياً. وسّعت C++14/17 constexpr، والآن يمكن تنفيذ العديد من حسابات وقت الترجمة بدوال constexpr.

📖 ملخص

الموضوع النقاط الأساسية
حسابات وقت الترجمة تكرار القوالب أو constexpr
سمات الأنواع مكتبة type_traits
SFINAE اختيار التحميلات بناءً على النوع
القوالب متعددة المتغيرات قبول عدد تعسفي من المعاملات
تعبيرات الطي C++17، تُبسط القوالب متعددة المتغيرات

📝 تمارين

  1. أساسي (الصعوبة ⭐): اكتب دالة constexpr لحساب المضروب، تُقيَّم في وقت الترجمة. تحقق من النتيجة باستخدام static_assert.

  2. متوسط (الصعوبة ⭐⭐): استخدم std::enable_if لتطبيق قالب دالة يُفعَّل فقط عندما يكون T نوعاً صحيحاً. الأنواع العشرية يجب أن تسبب خطأ ترجمة.

  3. متقدم (الصعوبة ⭐⭐⭐): استخدم القوالب متعددة المتغيرات لتطبيق دالة print_all تقبل أي عدد ونوع من المعاملات وتطبعها واحداً تلو الآخر. تلميح: استخدم التوسيع التكراري أو تعبيرات الطي (C++17).



الدرس التالي: ميزات C++17/20 الجديدة (#48)

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%