Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

القوالب المتغيرة

جزء من قسم 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) تعمل مع أي مزيج من أنواع وأعداد الوسائط.

challenge icon

التحدي

سهل

لنقم ببناء نظام تسجيل مرن يستخدم القوالب المتغيرة (variadic templates) للتعامل مع الرسائل التي تحتوي على أي عدد من الوسائط. ستقوم بإنشاء أدوات مساعدة يمكنها دمج القيم، وعد الوسائط، وطباعة مخرجات منسقة — كل ذلك باستخدام حزم المعلمات (parameter packs).

ستقوم بتنظيم الكود الخاص بك عبر ملفين:

  • Variadic.h: قم بتعريف دوال القوالب المتغيرة الخاصة بك هنا.

    أنشئ دالة printAll تقوم بطباعة جميع وسائطها مفصولة بمسافات، متبوعة بسطر جديد. استخدم النهج العودي (recursive approach) مع حالة أساسية (base case) تطبع فقط سطرًا جديدًا، وحالة عودية تطبع الوسيط الأول، ثم مسافة، ثم تستدعي نفسها عوديًا مع الوسائط المتبقية.

    أنشئ دالة countArgs تعيد عدد الوسائط الممرة إليها. استخدم sizeof... للحصول على حجم حزمة المعلمات.

    أنشئ دالة sum تجمع كل الوسائط الرقمية معًا وتعيد النتيجة. استخدم تعبير الطي (fold expression) مع عامل التشغيل +.

    أنشئ دالة product تضرب كل الوسائط الرقمية معًا وتعيد النتيجة. استخدم تعبير الطي (fold expression) مع عامل التشغيل *.

  • main.cpp: اقرأ أربعة مدخلات (كل منها في سطر منفصل):
    1. عدد صحيح (integer)
    2. عدد عشري (double)
    3. سلسلة نصية (string)
    4. حرف (character)

    قم باستعراض القوالب المتغيرة الخاصة بك من خلال:

    1. استدعاء printAll مع جميع المدخلات الأربعة وطباعة النتيجة.
    2. طباعة عدد الوسائط عند استدعاء countArgs مع العدد الصحيح، والعدد العشري، والحرف: Argument count: <count>
    3. حساب مجموع العدد الصحيح، والعدد العشري، والقيمتين 10 و 5، ثم طباعة: Sum: <result>
    4. حساب حاصل ضرب العدد الصحيح والقيمتين 2 و 3، ثم طباعة: Product: <result>
    5. استدعاء printAll مع السلسلة النصية فقط (وسيط واحد).
    6. استدعاء 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;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming