Kodax

Templates — C++ المتقدم

القوالب (Templates)

كتبت maxInt و maxDouble و maxString بنفس المنطق؟ القالب يكتب المنطق مرة واحدة، والمترجم يولّد نسخة لكل نوع تستخدمه. هذا هو أساس STL كلها: vector<int> و vector<string> نسختان من قالب واحد.

قالب دالة

#include <iostream>
#include <string>
using namespace std;

template <typename T>
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    cout << maximum(3, 7) << '\n';                        // T = int (استنتاج)
    cout << maximum(2.5, 1.5) << '\n';                    // T = double
    cout << maximum(string("كود"), string("برمجة")) << '\n';
    cout << maximum<double>(3, 4.5) << '\n';              // T محدد صراحةً
}

- typename و class داخل template<> متطابقتان.

- الاستنتاج: المترجم يستنتج T من المعاملات. maximum(3, 4.5) تفشل لأن T لا يمكن أن يكون int و double معاً — حددها صراحةً أو استخدم نوعين.

نوعان ونوع إرجاع مستنتج

template <typename T, typename U>
auto add(T a, U b) {          // C++14: نوع الإرجاع يُستنتج من return
    return a + b;
}
// add(3, 2.5) → 5.5 (double)

ستجد في الكود القديم الصيغة auto add(T a, U b) -> decltype(a + b) — نفس الفكرة قبل C++14.

قالب صنف

#include <stdexcept>
#include <vector>
using namespace std;

template <typename T>
class Stack {
    vector<T> items;
public:
    void push(const T& item) { items.push_back(item); }
    T pop() {
        if (items.empty()) throw runtime_error("المكدس فارغ");   // الاستثناءات بالتفصيل لاحقاً
        T top = items.back();
        items.pop_back();
        return top;
    }
    bool empty() const { return items.empty(); }
};
// Stack<int> s; s.push(1);   Stack<string> names;

منذ C++17 يستطيع المترجم أحياناً استنتاج معاملات قالب الصنف من المُنشئ (CTAD): vector v = {1, 2, 3}; تعني vector<int>.

معاملات غير نوعية

القالب يقبل قيماً ثابتة وقت الترجمة أيضاً:

#include <cstddef>

template <typename T, std::size_t N>
class FixedArray {
    T data[N] {};
public:
    constexpr std::size_t size() const { return N; }
    T& operator[](std::size_t i) { return data[i]; }
};
// FixedArray<int, 5> a;  — هكذا يعمل std::array<int, 5>

التخصيص (Specialization)

نسخة خاصة لنوع معين عندما يختلف السلوك:

#include <iostream>
#include <string>
using namespace std;

template <typename T>
void describe(const T& v) { cout << "قيمة: " << v << '\n'; }

template <>
void describe<bool>(const bool& v) { cout << "منطقي: " << (v ? "نعم" : "لا") << '\n'; }

int main() {
    describe(42);      // قيمة: 42
    describe(true);    // منطقي: نعم
}

أين يُكتب كود القالب؟

المترجم يحتاج تعريف القالب كاملاً عند كل استخدام ليولّد النسخة. لذلك تُكتب القوالب كاملة في ملفات الرأس (.h)، لا في .cpp. فصلها يعطي خطأ ربط undefined reference.

رسائل الأخطاء

إذا استخدمت القالب مع نوع لا يدعم العملية (مثلاً maximum مع صنف بلا >)، يظهر الخطأ داخل القالب برسالة طويلة. الدرس التالي (Concepts) يحل هذه المشكلة بوضع شروط واضحة على T.

تمرين

اكتب template <typename T> T sumAll(const vector<T>& v) يجمع العناصر، وجرّبه مع int و double و string. ماذا يحدث مع vector فارغ من string؟ (تلميح: ابدأ الجمع بـ T{}).

#include <iostream>
#include <vector>
using namespace std;

// Function Template
template <typename T>
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

// Template مع عدة أنواع
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
    return a + b;
}

// Class Template
template <typename T>
class Stack {
private:
    vector<T> items;

public:
    void push(T item) {
        items.push_back(item);
    }
    
    T pop() {
        if (isEmpty()) throw runtime_error("Stack is empty");
        T item = items.back();
        items.pop_back();
        return item;
    }
    
    T top() const {
        if (isEmpty()) throw runtime_error("Stack is empty");
        return items.back();
    }
    
    bool isEmpty() const {
        return items.empty();
    }
    
    size_t size() const {
        return items.size();
    }
};

// Template مع قيمة افتراضية
template <typename T = int, int SIZE = 10>
class Array {
private:
    T data[SIZE];
public:
    T& operator[](int index) {
        return data[index];
    }
    int getSize() const { return SIZE; }
};

int main() {
    // Function Template
    cout << "max(3, 5) = " << maximum(3, 5) << endl;
    cout << "max(3.5, 2.1) = " << maximum(3.5, 2.1) << endl;
    cout << "max('a', 'z') = " << maximum('a', 'z') << endl;
    
    cout << "add(3, 2.5) = " << add(3, 2.5) << endl;
    
    // Class Template
    Stack<int> intStack;
    intStack.push(10);
    intStack.push(20);
    intStack.push(30);
    
    cout << "\nStack:" << endl;
    while (!intStack.isEmpty()) {
        cout << intStack.pop() << " ";
    }
    cout << endl;
    
    Stack<string> strStack;
    strStack.push("مرحباً");
    strStack.push("عالم");
    cout << "Top: " << strStack.top() << endl;
    
    // Template مع قيم افتراضية
    Array<double, 5> arr;
    for (int i = 0; i < arr.getSize(); i++) {
        arr[i] = i * 1.5;
    }
    
    return 0;
}
اختبر فهمك