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

फ़ंक्शन टेम्पलेट और टाइप डिडक्शन

ऐसे फ़ंक्शन लिखना सीखें जो किसी भी टाइप के साथ काम करें। टेम्पलेट आर्गुमेंट डिडक्शन, नॉन-टाइप पैरामीटर, और टेम्पलेट हेडर में क्यों रहते हैं — यह सब समझें।

फ़ंक्शन टेम्पलेट की ज़रूरत क्यों?

टेम्पलेट के बिना, एक ऐसा फ़ंक्शन लिखना जो कई टाइप के लिए काम करे, यानी लॉजिक को बार-बार दोहराना। आप int max(int, int), double max(double, double), std::string max(std::string, std::string) लिखेंगे — हर टाइप के लिए एक ही लॉजिक दोहराया हुआ। फ़ंक्शन टेम्पलेट आपको एल्गोरिदम सिर्फ़ एक बार लिखने देते हैं, और टाइप-विशिष्ट वर्शन कंपाइलर अपने-आप जनरेट कर देता है।

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

टेम्पलेट सिंटैक्स

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

function_template.cpp
#include <iostream>
#include <string>

// 'typename' and 'class' are interchangeable here
template <typename T>
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

// Equivalent — 'class' means the same thing
template <class T>
T minimum(T a, T b) {
    return (a < b) ? a : b;
}

int main() {
    std::cout << maximum(3, 7)          << '\n'; // deduces T = int
    std::cout << maximum(3.14, 2.71)    << '\n'; // deduces T = double
    std::cout << maximum<std::string>("hello", "world") << '\n';
    // explicit instantiation needed — "hello" is const char*, not std::string
}

टेम्पलेट आर्गुमेंट डिडक्शन के नियम

कंपाइलर फ़ंक्शन आर्गुमेंट के टाइप को पैरामीटर पैटर्न से मिलाकर टेम्पलेट आर्गुमेंट डिडक्ट करता है। मुख्य नियम ये हैं:

1. एग्ज़ैक्ट मैच को प्राथमिकता — maximum(3, 7) में T = int डिडक्ट होता है क्योंकि दोनों आर्गुमेंट int हैं।
2. डिडक्शन के दौरान कोई इम्प्लिसिट कन्वर्ज़न नहीं — maximum(3, 7.0) एक एरर है: कंपाइलर को पहले आर्गुमेंट से T = int और दूसरे से T = double दिखता है, और वह किसी एक को चुनने से मना कर देता है।
3. ऐरे-से-पॉइंटर डिके — जब T वैल्यू पैरामीटर के रूप में आता है, तो ऐरे पॉइंटर में डिके हो जाते हैं। ऐरे का टाइप और साइज़ पकड़ने के लिए T(&)[N] का इस्तेमाल करें।
4. वैल्यू पैरामीटर से टॉप-लेवल const/volatile हटा दिया जाता है, लेकिन रेफ़रेंस/पॉइंटर पैरामीटर में वह बना रहता है।
5. रेफ़रेंस कोलैप्सिंग — अगर T रेफ़रेंस टाइप के रूप में डिडक्ट होता है (फ़ॉरवर्डिंग रेफ़रेंस में), तो रेफ़रेंस कोलैप्सिंग के नियम लागू होते हैं: T& & बन जाता है T&, T& && बन जाता है T&, T&& & बन जाता है T&, T&& && बन जाता है T&&।

जब डिडक्शन फ़ेल हो जाए या अस्पष्ट हो, तो आपको टेम्पलेट आर्गुमेंट स्पष्ट रूप से (explicitly) देने होंगे: maximum(3, 7.0)।

नॉन-टाइप टेम्पलेट पैरामीटर

टेम्पलेट पैरामीटर सिर्फ़ टाइप तक सीमित नहीं हैं। नॉन-टाइप टेम्पलेट पैरामीटर (NTTP) कंपाइल-टाइम वैल्यू स्वीकार करते हैं, जैसे इंटीजर, पॉइंटर, या (C++20 से) फ़्लोटिंग-पॉइंट और क्लास टाइप। कंपाइल-टाइम कॉन्स्टेंट को टाइप सिस्टम में जोड़ने के लिए ये बहुत शक्तिशाली हैं।

non_type_params.cpp
#include <iostream>
#include <array>
#include <cstddef>

// Non-type parameter: N is a compile-time size_t value
template <typename T, std::size_t N>
T sum(const std::array<T, N>& arr) {
    T total{};
    for (const auto& elem : arr) {
        total += elem;
    }
    return total;
}

// C++20: auto non-type parameter accepts any structural type
template <auto Value>
constexpr auto doubled = Value * 2;

int main() {
    std::array<int, 4> nums = {1, 2, 3, 4};
    std::cout << sum(nums) << '\n';     // 10, deduces T=int, N=4

    std::cout << doubled<21> << '\n';   // 42
    std::cout << doubled<3.14> << '\n'; // 6.28 (C++20)
}
टिप्पणी

टेम्पलेट आर्गुमेंट के हर अलग संयोजन पर कंपाइलर एक अलग इंस्टैंशिएशन जनरेट करता है — यानी आपकी बाइनरी में फ़ंक्शन की एक अलग कॉपी। maximum, maximum और maximum को कॉल करने पर तीन फ़ंक्शन बनते हैं। आम तौर पर यह ठीक है, लेकिन बड़े कोडबेस में टेम्पलेट कोड ब्लोट (बड़ी बाइनरी, लंबा कंपाइल समय) का कारण बन सकते हैं। इसे कम करने के उपायों में शामिल हैं: इंस्टैंशिएशन कहाँ हों, यह तय करने के लिए extern template डिक्लेरेशन इस्तेमाल करना, टाइप-स्वतंत्र कोड को टेम्पलेट से बाहर निकालना, और जब रनटाइम पॉलीमॉर्फ़िज़्म स्वीकार्य हो तब टाइप इरेज़र का इस्तेमाल करना।

सामान्य गलती

क्योंकि इंस्टैंशिएट करने के लिए कंपाइलर को पूरी टेम्पलेट डेफ़िनिशन देखनी होती है, इसलिए टेम्पलेट को सामान्य फ़ंक्शन की तरह .h / .cpp फ़ाइलों में नहीं बाँटा जा सकता। अगर आप टेम्पलेट डेफ़िनिशन .cpp फ़ाइल में रखकर उसे किसी दूसरी ट्रांसलेशन यूनिट से इस्तेमाल करने की कोशिश करेंगे, तो आपको लिंकर एरर (undefined reference) मिलेगा। आम समाधान यह है कि पूरी टेम्पलेट डेफ़िनिशन हेडर फ़ाइल में रखी जाए। अगर कंपाइल समय समस्या बन जाए, तो आप एक्सप्लिसिट इंस्टैंशिएशन इस्तेमाल कर सकते हैं: डेफ़िनिशन .cpp फ़ाइल में रखें और जिन स्पेशलाइज़ेशन की आपको ज़रूरत है उन्हें स्पष्ट रूप से इंस्टैंशिएट करने के लिए template int maximum(int, int); जोड़ दें।

एक्सप्लिसिट इंस्टैंशिएशन

आप स्पष्ट रूप से नियंत्रित कर सकते हैं कि टेम्पलेट कहाँ और कब इंस्टैंशिएट हों। ट्रांसलेशन यूनिट्स में बेवजह के दोहराए गए इंस्टैंशिएशन रोककर, यह बड़े प्रोजेक्ट में कंपाइल समय घटा सकता है।

explicit_instantiation.h
// ---- maximum.h ----
template <typename T>
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

// Suppress implicit instantiation in every TU that includes this header
extern template int maximum<int>(int, int);
extern template double maximum<double>(double, double);

// ---- maximum.cpp ----
#include "maximum.h"

// Explicit instantiation: generate code here, once
template int maximum<int>(int, int);
template double maximum<double>(double, double);
मुख्य बातें
  • फ़ंक्शन टेम्पलेट ब्लूप्रिंट हैं — कंपाइलर टेम्पलेट आर्गुमेंट के हर सेट के लिए ठोस फ़ंक्शन जनरेट करता है
  • टेम्पलेट पैरामीटर लिस्ट में typename और class आपस में बदले जा सकते हैं
  • टेम्पलेट आर्गुमेंट डिडक्शन इम्प्लिसिट कन्वर्ज़न नहीं करता — अस्पष्ट डिडक्शन एरर होते हैं
  • नॉन-टाइप टेम्पलेट पैरामीटर कंपाइल-टाइम वैल्यू को टाइप सिस्टम में जोड़ते हैं
  • टेम्पलेट को हेडर में परिभाषित करना ज़रूरी है (या एक्सप्लिसिट इंस्टैंशिएशन इस्तेमाल करें), क्योंकि इंस्टैंशिएट करने के लिए कंपाइलर को पूरी डेफ़िनिशन चाहिए

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

(15 XP)

1. टेम्पलेट `template<typename T> T maximum(T a, T b)` के साथ `maximum(3, 7.0)` कॉल करने पर क्या होता है?

2. टेम्पलेट डेफ़िनिशन आम तौर पर हेडर फ़ाइलों में क्यों रखनी पड़ती हैं?

3. `extern template int maximum<int>(int, int);` का उद्देश्य क्या है?