सामग्री पर जाएं

क्लास टेम्पलेट और CTAD

क्लास टेम्पलेट से जेनेरिक डेटा स्ट्रक्चर बनाएँ। मेंबर डेफ़िनिशन, डिफ़ॉल्ट आर्गुमेंट, और C++17 का CTAD आपको टेम्पलेट आर्गुमेंट छोड़ने की सुविधा कैसे देता है — यह समझें।

क्लास टेम्पलेट

क्लास टेम्पलेट क्लासों का एक परिवार परिभाषित करता है, जो एक या अधिक टाइप (या नॉन-टाइप वैल्यू) से पैरामीटराइज़्ड होता है। स्टैंडर्ड लाइब्रेरी क्लास टेम्पलेट पर ही बनी है: std::vector, std::map, std::optional, std::shared_ptr। जब आप std::vector लिखते हैं, तो कंपाइलर T = int के साथ पूरी क्लास इंस्टैंशिएट करके एक ठोस टाइप जनरेट करता है।

क्लास टेम्पलेट फ़ंक्शन टेम्पलेट जैसे ही नियमों का पालन करते हैं: उन्हें हेडर में परिभाषित करना होता है, और टेम्पलेट आर्गुमेंट का हर अलग सेट एक अलग टाइप बनाता है। Stack और Stack पूरी तरह असंबंधित टाइप हैं — उनके बीच कोई इनहेरिटेंस संबंध नहीं है।

एक जेनेरिक स्टैक

यह एक सरल जेनेरिक स्टैक है। ध्यान दें कि क्लास बॉडी के बाहर परिभाषित मेंबर फ़ंक्शन में टेम्पलेट पैरामीटर लिस्ट दोबारा लिखनी पड़ती है।

stack.hpp
#include <vector>
#include <stdexcept>
#include <iostream>

template <typename T>
class Stack {
private:
    std::vector<T> elements_;

public:
    void push(const T& value);
    void pop();
    const T& top() const;
    bool empty() const { return elements_.empty(); }
    std::size_t size() const { return elements_.size(); }
};

// Member definitions outside the class require the template header
template <typename T>
void Stack<T>::push(const T& value) {
    elements_.push_back(value);
}

template <typename T>
void Stack<T>::pop() {
    if (elements_.empty()) {
        throw std::out_of_range("Stack<>::pop(): empty stack");
    }
    elements_.pop_back();
}

template <typename T>
const T& Stack<T>::top() const {
    if (elements_.empty()) {
        throw std::out_of_range("Stack<>::top(): empty stack");
    }
    return elements_.back();
}

int main() {
    Stack<int> intStack;
    intStack.push(42);
    intStack.push(17);
    std::cout << intStack.top() << '\n'; // 17
    intStack.pop();
    std::cout << intStack.top() << '\n'; // 42
}

एकाधिक पैरामीटर और डिफ़ॉल्ट

क्लास टेम्पलेट में एकाधिक टेम्पलेट पैरामीटर और डिफ़ॉल्ट टेम्पलेट आर्गुमेंट हो सकते हैं, ठीक वैसे ही जैसे फ़ंक्शन पैरामीटर के डिफ़ॉल्ट मान हो सकते हैं। डिफ़ॉल्ट आर्गुमेंट बाएँ से दाएँ हल किए जाते हैं।

stack_defaults.cpp
#include <vector>
#include <deque>
#include <iostream>

// Container defaults to std::deque<T> — matches std::stack's design
template <typename T, typename Container = std::deque<T>>
class Stack {
private:
    Container elements_;

public:
    void push(const T& value) { elements_.push_back(value); }
    void pop()                { elements_.pop_back(); }
    const T& top() const      { return elements_.back(); }
    bool empty() const        { return elements_.empty(); }
};

int main() {
    Stack<int> s1;                         // uses deque<int>
    Stack<int, std::vector<int>> s2;       // uses vector<int>

    s1.push(10);
    s2.push(20);
    std::cout << s1.top() << '\n'; // 10
    std::cout << s2.top() << '\n'; // 20
}

क्लास टेम्पलेट आर्गुमेंट डिडक्शन (CTAD)

C++17 से पहले, क्लास टेम्पलेट के इंस्टेंस बनाते समय आपको हमेशा टेम्पलेट आर्गुमेंट बताने पड़ते थे: std::pair p(1, 3.14);। C++17 में आया CTAD (Class Template Argument Deduction) कंपाइलर को कंस्ट्रक्टर आर्गुमेंट से क्लास टेम्पलेट आर्गुमेंट डिडक्ट करने देता है, ठीक फ़ंक्शन टेम्पलेट आर्गुमेंट डिडक्शन की तरह।

CTAD के साथ: std::pair p(1, 3.14); — कंपाइलर std::pair डिडक्ट कर लेता है। यह std::vector, std::tuple, std::optional, std::array और आपके अपने क्लास टेम्पलेट के लिए काम करता है।

CTAD डिडक्शन गाइड इस्तेमाल करता है — या तो इम्प्लिसिट (कंस्ट्रक्टर से जनरेट किए गए) या एक्सप्लिसिट (यूज़र-डिफ़ाइंड)। कंपाइलर हर गाइड को आज़माता है और सबसे अच्छा मैच चुनता है।

CTAD और डिडक्शन गाइड

यूज़र-डिफ़ाइंड डिडक्शन गाइड कंपाइलर को बताते हैं कि कंस्ट्रक्टर आर्गुमेंट को टेम्पलेट आर्गुमेंट से कैसे मैप करना है। ये ख़ास तौर पर तब उपयोगी हैं जब कंस्ट्रक्टर आर्गुमेंट का टाइप सीधे टेम्पलेट पैरामीटर से मेल न खाता हो।

ctad_example.cpp
#include <iostream>
#include <cstddef>

template <typename T, std::size_t N>
struct Array {
    T data[N];
};

// Without a deduction guide, CTAD cannot deduce T and N.
// This explicit deduction guide maps brace-init with N elements
// of the same type T to Array<T, N>.
template <typename First, typename... Rest>
Array(First, Rest...) -> Array<First, 1 + sizeof...(Rest)>;

// Standard library example: std::vector CTAD from iterators
#include <vector>
#include <list>

int main() {
    // CTAD with our custom deduction guide
    Array arr{1, 2, 3, 4, 5};  // deduces Array<int, 5>
    std::cout << arr.data[2] << '\n'; // 3

    // Standard library CTAD examples (C++17)
    std::pair p(42, 3.14);       // pair<int, double>
    std::vector v{1, 2, 3};     // vector<int>
    std::tuple t(1, 2.0, 'a');  // tuple<int, double, char>

    // CTAD from iterator range
    std::list<int> lst{10, 20, 30};
    std::vector v2(lst.begin(), lst.end()); // vector<int>
}

मेंबर टेम्पलेट

क्लास टेम्पलेट के मेंबर फ़ंक्शन खुद भी टेम्पलेट हो सकते हैं, जिससे अतिरिक्त टेम्पलेट पैरामीटर जुड़ जाते हैं। इसे मेंबर टेम्पलेट कहते हैं। एक आम उपयोग यह है कि किसी संबंधित लेकिन अलग इंस्टैंशिएशन से असाइनमेंट या कंस्ट्रक्शन संभव हो सके।

उदाहरण के लिए, आप शायद Stack से Stack में असाइन करना चाहें — लेकिन चूँकि ये असंबंधित टाइप हैं, डिफ़ॉल्ट कॉपी असाइनमेंट काम नहीं करेगा। मेंबर टेम्पलेट असाइनमेंट ऑपरेटर इसे हल करता है:

template <typename T>
class Stack {
public:
    template <typename U>
    Stack& operator=(const Stack<U>& other);
};

इस मेंबर टेम्पलेट के अंदर U, T से अलग हो सकता है। जब तक U को T में बदला जा सकता है, तब तक बॉडी other से एलिमेंट कॉपी कर सकती है। यह पैटर्न स्टैंडर्ड लाइब्रेरी में बहुत इस्तेमाल होता है — std::shared_ptr मेंबर टेम्पलेट कंस्ट्रक्टर के ज़रिए std::shared_ptr में बदल जाता है।

सामान्य गलती

जब आप किसी क्लास टेम्पलेट को स्पेशलाइज़ करते हैं, तो वह स्पेशलाइज़ेशन एक पूरी तरह नई क्लास होता है — वह प्राइमरी टेम्पलेट से कोई मेंबर इनहेरिट नहीं करता। आपको हर मेंबर फ़ंक्शन और डेटा मेंबर दोबारा परिभाषित करना होगा। स्पेशलाइज़ेशन में किसी मेंबर को दोबारा परिभाषित करना भूल जाने पर, उस मेंबर के इस्तेमाल के समय कंपाइल एरर आता है। साथ ही, पार्शियल स्पेशलाइज़ेशन सिर्फ़ क्लास (और वेरिएबल) टेम्पलेट के लिए उपलब्ध है, फ़ंक्शन टेम्पलेट के लिए नहीं। स्पेशलाइज़ेशन को हम अगले चैप्टर में विस्तार से देखेंगे।

मुख्य बातें
  • क्लास टेम्पलेट क्लासों के परिवार परिभाषित करते हैं — आर्गुमेंट का हर अलग सेट एक अलग टाइप बनाता है
  • क्लास के बाहर परिभाषित मेंबर फ़ंक्शन में template हेडर दोहराना होता है और ClassName:: इस्तेमाल करना होता है
  • डिफ़ॉल्ट टेम्पलेट आर्गुमेंट बाएँ से दाएँ काम करते हैं, ठीक डिफ़ॉल्ट फ़ंक्शन आर्गुमेंट की तरह
  • CTAD (C++17) कंपाइलर को कंस्ट्रक्टर आर्गुमेंट से क्लास टेम्पलेट आर्गुमेंट डिडक्ट करने देता है
  • एक्सप्लिसिट डिडक्शन गाइड कंस्ट्रक्टर आर्गुमेंट के पैटर्न को टेम्पलेट आर्गुमेंट से मैप करते हैं
  • मेंबर टेम्पलेट एक ही क्लास टेम्पलेट के अलग-अलग इंस्टैंशिएशन के बीच ऑपरेशन की अनुमति देते हैं

क्विज़ — अपना ज्ञान परखें

(15 XP)

1. `template <typename T, typename Container = std::deque<T>> class Stack` दिए जाने पर `Stack<int>` का टाइप क्या है?

2. CTAD (C++17) क्या है?

3. क्लास टेम्पलेट को स्पेशलाइज़ करने पर प्राइमरी टेम्पलेट के मेंबर का क्या होता है?