C++ में template और typename: कहाँ लगाएं और क्यों ये ज़रूरी हैं
\n\nअगर आपने कभी C++ में टेम्पलेट के साथ कोड लिखा या पढ़ा है, तो आपने ज़रूर ऐसी स्थिति देखी होगी जब कंपाइलर अचानक से एरर देने लगता है। आपको लगता है कि कोड बिल्कुल सही है, लेकिन कंपाइलर बार-बार template या typename लगाने की ज़िद करता है। ये बात आपको जानी-पहचानी लग रही है? तो आप सही जगह पर हैं।
यह सवाल Stack Overflow पर सबसे लोकप्रिय सवालों में से एक है (हज़ारों व्यूज़ और दर्जनों जवाब)। और इसमें कोई आश्चर्य नहीं: अनुभवी डेवलपर्स भी कभी-कभी भ्रमित हो जाते हैं कि इन कीवर्ड्स का उपयोग कहाँ करना है। इस लेख में हम एक बार और हमेशा के लिए समझ लेंगे: C++ में template और typename कहाँ, कब और क्यों लिखना चाहिए। और सबसे ज़रूरी बात - हम समझेंगे कि इस जाल में कैसे न फँसें।
यह समस्या क्यों पैदा हुई?
\n\nइस समस्या की जड़ है डिपेंडेंट नेम्स (dependent names)। जब आप टेम्पलेट कोड लिखते हैं, तो कंपाइलर तुरंत यह नहीं समझ पाता कि वह किसके साथ काम कर रहा है: एक टाइप के साथ या किसी और चीज़ के साथ। ऐसा इसलिए क्योंकि टेम्पलेट के पैरामीटर अभी ज्ञात नहीं हैं। कंपाइलर इंस्टेंशिएशन के समय तक पार्सिंग को टाल देता है, लेकिन उसे प्राथमिक पार्सिंग के चरण में ही नाम की सिंटैक्टिक भूमिका जाननी होती है।
\n\nकल्पना करें कि आप लिख रहे हैं:
\n\ntemplate<typename T>\nvoid foo() {\n T::something * p;\n}\n\n\nयह क्या है? T::something पर पॉइंटर p की घोषणा? या T::something का p से गुणा? अतिरिक्त जानकारी के बिना, कंपाइलर नहीं जानता। और इसी अस्पष्टता को हल करने के लिए typename और template कीवर्ड बनाए गए।
typename कीवर्ड: जब टाइप स्पष्ट न हो
\n\nमुख्य नियम
\n\ntypename कीवर्ड का उपयोग कंपाइलर को यह बताने के लिए किया जाता है: "अरे, जो आगे आ रहा है वह एक टाइप है, कोई और चीज़ नहीं"। इसकी ज़रूरत उन मामलों में होती है जब नाम टेम्पलेट पैरामीटर पर निर्भर करता है, और कंपाइलर स्वयं यह निर्धारित नहीं कर सकता कि यह एक टाइप है।
आइए एक क्लासिक उदाहरण देखें:
\n\ntemplate<typename T>\nclass MyClass {\npublic:\n // typename के बिना — कंपाइलेशन एरर\n typename T::value_type getValue() const {\n return data;\n }\nprivate:\n T data;\n};\n\n\nयहाँ T::value_type एक डिपेंडेंट नेम है। typename के बिना, कंपाइलर सोचेगा कि value_type एक स्टैटिक डेटा मेंबर या नेस्टेड क्लास है, लेकिन टाइप नहीं। जबकि हम जानते हैं कि यह एक टाइप है (उदाहरण के लिए, int, अगर T = std::vector<int> है)।
और कहाँ typename की ज़रूरत है?
\n\nनियम सरल है: निम्नलिखित संदर्भों में डिपेंडेंट टाइप नेम्स से पहले typename अनिवार्य है:
- \n
- वेरिएबल और क्लास मेंबर की घोषणाओं में \n
- फंक्शन पैरामीटर्स में \n
- रिटर्न टाइप्स में \n
- using-डिक्लेरेशन (टाइप एलियास) में \n
- बेस क्लासेस में (अगर बेस क्लास एक डिपेंडेंट टाइप है) \n
using का उदाहरण:
\n\ntemplate<typename T>\nclass Container {\n using Iterator = typename T::iterator; // typename अनिवार्य है!\n // ...\n};\n\n\ntypename कब ज़रूरी नहीं है?
\n\nकुछ महत्वपूर्ण अपवाद हैं जब typename की आवश्यकता नहीं होती:
- \n
- टेम्पलेट पैरामीटर लिस्ट में:
template<typename T>— यहाँtypenameसिंटैक्स का हिस्सा है \n - बेस क्लासेस में:
class Derived : public Base<T>::Nested— यहाँtypenameकी ज़रूरत नहीं \n - कंस्ट्रक्टर की इनिशियलाइज़ेशन लिस्ट में \n
- उस संदर्भ में जहाँ नाम स्पष्ट रूप से एक टाइप है (जैसे
newके बाद) \n
template कीवर्ड: जब टेम्पलेट के अंदर टेम्पलेट हो
\n\nसमस्या का सार
\n\ntemplate के साथ स्थिति उलटी है। इसकी ज़रूरत तब होती है जब आप किसी टेम्पलेट मेंबर को एक्सेस कर रहे हों