القوالب المتغيرة
جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 67 من 104.
ماذا لو كنت بحاجة إلى دالة تقبل أي عدد من الوسائط من أي نوع؟ تتطلب القوالب التقليدية منك تحديد عدد معلمات النوع التي تحتاجها بالضبط. تحل القوالب المتغيرة (Variadic templates) هذه المشكلة من خلال السماح للقوالب بقبول عدد عشوائي من وسائط القالب.
تستخدم الصيغة ... (علامة الحذف) لإنشاء حزمة معلمات:
template <typename... Args>
void printAll(Args... args) {
// Args هي حزمة معلمات قالب
// args هي حزمة معلمات دالة
}لمعالجة الوسائط، عادةً ما تستخدم العودية (recursion) مع حالة أساسية. تتوسع حزمة المعلمات (parameter pack) وسيطًا واحدًا في كل مرة حتى لا يتبقى شيء:
// الحالة الأساسية: لم يتبق أي وسائط
void print() {
std::cout << std::endl;
}
// الحالة العودية: معالجة الوسيط الأول، ثم الاستدعاء الذاتي مع الباقي
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << " ";
print(rest...); // توسيع الوسائط المتبقية
}
print(1, 3.14, "hello", 'x'); // المخرجات: 1 3.14 hello xيمكنك أيضًا استخدام تعبيرات الطي (fold expressions) (C++17) لإجراء عمليات أبسط بدون تكرار صريح:
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // تعبير الطي: يجمع كل الوسائط
}
std::cout << sum(1, 2, 3, 4) << std::endl; // المخرج: 10تدعم القوالب المتغيرة (Variadic templates) العديد من ميزات المكتبة القياسية مثل std::make_unique و std::tuple و std::function. فهي تُمكّن من إنشاء دوال آمنة من حيث النوع (type-safe) تعمل مع أي مزيج من أنواع وأعداد الوسائط.
التحدي
سهللنقم ببناء نظام تسجيل مرن يستخدم القوالب المتغيرة (variadic templates) للتعامل مع الرسائل التي تحتوي على أي عدد من الوسائط. ستقوم بإنشاء أدوات مساعدة يمكنها دمج القيم، وعد الوسائط، وطباعة مخرجات منسقة — كل ذلك باستخدام حزم المعلمات (parameter packs).
ستقوم بتنظيم الكود الخاص بك عبر ملفين:
Variadic.h: قم بتعريف دوال القوالب المتغيرة الخاصة بك هنا.أنشئ دالة
printAllتقوم بطباعة جميع وسائطها مفصولة بمسافات، متبوعة بسطر جديد. استخدم النهج العودي (recursive approach) مع حالة أساسية (base case) تطبع فقط سطرًا جديدًا، وحالة عودية تطبع الوسيط الأول، ثم مسافة، ثم تستدعي نفسها عوديًا مع الوسائط المتبقية.أنشئ دالة
countArgsتعيد عدد الوسائط الممرة إليها. استخدمsizeof...للحصول على حجم حزمة المعلمات.أنشئ دالة
sumتجمع كل الوسائط الرقمية معًا وتعيد النتيجة. استخدم تعبير الطي (fold expression) مع عامل التشغيل+.أنشئ دالة
productتضرب كل الوسائط الرقمية معًا وتعيد النتيجة. استخدم تعبير الطي (fold expression) مع عامل التشغيل*.main.cpp: اقرأ أربعة مدخلات (كل منها في سطر منفصل):- عدد صحيح (integer)
- عدد عشري (double)
- سلسلة نصية (string)
- حرف (character)
قم باستعراض القوالب المتغيرة الخاصة بك من خلال:
- استدعاء
printAllمع جميع المدخلات الأربعة وطباعة النتيجة. - طباعة عدد الوسائط عند استدعاء
countArgsمع العدد الصحيح، والعدد العشري، والحرف:Argument count: <count> - حساب مجموع العدد الصحيح، والعدد العشري، والقيمتين 10 و 5، ثم طباعة:
Sum: <result> - حساب حاصل ضرب العدد الصحيح والقيمتين 2 و 3، ثم طباعة:
Product: <result> - استدعاء
printAllمع السلسلة النصية فقط (وسيط واحد). - استدعاء
printAllبدون وسائط (يجب أن يطبع سطرًا جديدًا فقط).
على سبيل المثال، مع المدخلات 5، و 2.5، و Hello، و X:
5 2.5 Hello X
Argument count: 3
Sum: 22.5
Product: 30
Hello
لاحظ كيف تتعامل printAll مع أي عدد من الوسائط من أنواع مختلطة، من أربعة وسائط وصولاً إلى صفر. تستخدم دالتا sum و product تعبيرات الطي لدمج جميع القيم بأناقة دون عودية صريحة. توضح دالة countArgs كيف تمنحك sizeof... حجم الحزمة في وقت التجميع.
ورقة مرجعية
تسمح القوالب المتغيرة (Variadic templates) للقوالب بقبول عدد عشوائي من وسائط القالب باستخدام بناء الجملة ... (علامة الحذف) لإنشاء حزمة معلمات (parameter pack):
template <typename... Args>
void printAll(Args... args) {
// Args هي حزمة معلمات قالب
// args هي حزمة معلمات دالة
}معالجة الوسائط باستخدام العودية (recursion) مع حالة أساسية:
// الحالة الأساسية: لم يتبق أي وسائط
void print() {
std::cout << std::endl;
}
// الحالة العودية: معالجة الوسيط الأول، ثم الاستدعاء الذاتي مع الباقي
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << " ";
print(rest...); // توسيع الوسائط المتبقية
}
print(1, 3.14, "hello", 'x'); // المخرجات: 1 3.14 hello xاستخدم sizeof... للحصول على عدد الوسائط في حزمة المعلمات:
template <typename... Args>
size_t countArgs(Args... args) {
return sizeof...(args);
}استخدم تعبيرات الطي (fold expressions) (C++17) لعمليات أبسط بدون عودية صريحة:
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // تعبير الطي: يجمع كل الوسائط
}
std::cout << sum(1, 2, 3, 4) << std::endl; // المخرجات: 10تعمل تعبيرات الطي مع عوامل تشغيل أخرى مثل الضرب:
template <typename... Args>
auto product(Args... args) {
return (args * ...); // يضرب كل الوسائط
}جرّب بنفسك
#include <iostream>
#include <string>
#include "Variadic.h"
using namespace std;
int main() {
// قراءة المدخلات
int intVal;
double doubleVal;
string strVal;
char charVal;
cin >> intVal;
cin >> doubleVal;
cin >> strVal;
cin >> charVal;
// TODO: استدعاء printAll مع جميع المدخلات الأربعة
// TODO: طباعة عدد الوسائط باستخدام countArgs مع intVal و doubleVal و charVal
// التنسيق: "Argument count: <count>"
// TODO: حساب وطباعة مجموع intVal و doubleVal و 10 و 5
// التنسيق: "Sum: <result>"
// TODO: حساب وطباعة حاصل ضرب intVal و 2 و 3
// التنسيق: "Product: <result>"
// TODO: استدعاء printAll مع السلسلة النصية فقط
// TODO: استدعاء printAll بدون أي وسائط
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامة