C++: مقدمة في ما وراء برمجة القوالب
آخر تحديث: 2026-08-26
في الدرس 46 تعلمنا عن المؤشرات الذكية المتقدمة.
الآن، سنتطرق إلى "السحر الأسود" في C++ - ما وراء برمجة القوالب.
ما وراء برمجة القوالب هي تقنية تُنفذ الكود في وقت الترجمة، مما يمكن أن يحسن أداء وقت التنفيذ بشكل كبير.
1. نظرة عامة على ما وراء برمجة القوالب
(1) 1.1 ما هي ما وراء برمجة القوالب؟
ما وراء برمجة القوالب (TMP) هي تقنية تستخدم القوالب لإجراء حسابات في وقت الترجمة.
الخصائص:
- الحسابات تتم في وقت الترجمة، مع صفر تكلفة في وقت التنفيذ
- الكود معقد، وأوقات الترجمة طويلة
- رسائل الخطأ صعبة القراءة
(2) 1.2 لماذا نستخدم ما وراء برمجة القوالب؟
| الميزة | الوصف |
|---|---|
| الأداء | حسابات وقت الترجمة، أسرع في وقت التنفيذ |
| أمان الأنواع | فحص الأنواع في وقت الترجمة |
| التعميم | كتابة كود عام حقاً |
2. الحسابات في وقت الترجمة
(1) 2.1 المضروب في وقت الترجمة
مثال: حساب المضروب باستخدام القوالب (الصعوبة ⭐⭐⭐)
▶ مثال 1: مثال برمجي (الصعوبة ⭐)
#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;
}
الخرج:
5! =
▶ مثال 2: مثال برمجي (الصعوبة ⭐)
💡 نصيحة:
- استخدم تخصيص القالب كشرط إنهاء التكرار
static const int valueهو ثابت وقت ترجمة
(2) 2.2 دوال constexpr (C++11، موصى بها)
أدخلت C++11 constexpr، مما جعل حسابات وقت الترجمة أبسط.
مثال: حساب المضروب باستخدام constexpr (الصعوبة ⭐⭐)
#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;
}
الخرج:
5! =
💡 نصيحة:
constexprأكثر إيجازاً من ما وراء برمجة القوالب - يُفضّلconstexpr
3. سمات الأنواع
(1) 3.1 ما هي سمات الأنواع؟
سمات الأنواع هي تقنيات للاستعلام عن معلومات الأنواع أو تعديلها في وقت الترجمة.
مثال: استخدام type_traits للتحقق من الأنواع (الصعوبة ⭐⭐)
#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 سمات أنواع مخصصة
مثال: اكتشاف ما إذا كانت الفئة تمتلك دالة عضو (الصعوبة ⭐⭐⭐⭐)
#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 لاختيار التحميلات الزائدة (الصعوبة ⭐⭐⭐⭐)
#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;
}
الخرج:
(program output)
5. القوالب متعددة المتغيرات
(1) 5.1 ما هي القوالب متعددة المتغيرات؟
القوالب متعددة المتغيرات تسمح للقوالب بقبول عدد تعسفي من المعاملات.
مثال: طباعة أي عدد من المعاملات (الصعوبة ⭐⭐⭐)
#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 تعبيرات الطي، التي تُبسط القوالب متعددة المتغيرات.
مثال: جمع (الصعوبة ⭐⭐)
#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 في وقت الترجمة (الصعوبة ⭐)
#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;
}
الخرج:
5^2 =
3^3 =
❓ أسئلة شائعة
س: هل ما وراء برمجة القوالب صعبة التعلم؟ ج: نعم. يُنصح بإتقان القوالب الأساسية أولاً قبل تعلم ما وراء البرمجة. معظم المشاريع لا تحتاج ما وراء البرمجة.
constexpr، والآن يمكن تنفيذ العديد من حسابات وقت الترجمة بدوال constexpr.📖 ملخص
| الموضوع | النقاط الأساسية |
|---|---|
| حسابات وقت الترجمة | تكرار القوالب أو constexpr |
| سمات الأنواع | مكتبة type_traits |
| SFINAE | اختيار التحميلات بناءً على النوع |
| القوالب متعددة المتغيرات | قبول عدد تعسفي من المعاملات |
| تعبيرات الطي | C++17، تُبسط القوالب متعددة المتغيرات |
📝 تمارين
-
أساسي (الصعوبة ⭐): اكتب دالة constexpr لحساب المضروب، تُقيَّم في وقت الترجمة. تحقق من النتيجة باستخدام static_assert.
-
متوسط (الصعوبة ⭐⭐): استخدم std::enable_if لتطبيق قالب دالة يُفعَّل فقط عندما يكون T نوعاً صحيحاً. الأنواع العشرية يجب أن تسبب خطأ ترجمة.
-
متقدم (الصعوبة ⭐⭐⭐): استخدم القوالب متعددة المتغيرات لتطبيق دالة print_all تقبل أي عدد ونوع من المعاملات وتطبعها واحداً تلو الآخر. تلميح: استخدم التوسيع التكراري أو تعبيرات الطي (C++17).
- ما وراء برمجة القوالب: حسابات وقت الترجمة، صفر تكلفة وقت التنفيذ
- تقييم constexpr في وقت الترجمة
- SFINAE: فشل الاستبدال ليس خطأً
- سمات الأنواع تستعلم خصائص الأنواع
- القوالب متعددة المتغيرات تتعامل مع أعداد تعسفية من المعاملات
الدرس التالي: ميزات C++17/20 الجديدة (#48)