संक्षिप्त जवाब: x
एक आश्रित नाम बनाने के लिए , ताकि लुकअप टाल दिया जाए जब तक कि टेम्प्लेट पैरामीटर ज्ञात न हो जाए।
दीर्घ उत्तर: जब कोई कंपाइलर कोई टेम्प्लेट देखता है, तो उसे टेम्प्लेट पैरामीटर को देखे बिना, तुरंत कुछ जाँच करना माना जाता है। पैरामीटर ज्ञात होने तक अन्य को स्थगित कर दिया जाता है। इसे दो-चरण संकलन कहा जाता है, और MSVC ऐसा नहीं करता है, लेकिन यह मानक द्वारा आवश्यक है और अन्य प्रमुख संकलक द्वारा कार्यान्वित किया जाता है। यदि आप चाहें, तो कंपाइलर को जैसे ही यह दिखाई देता है (किसी प्रकार का आंतरिक पार्स ट्री प्रतिनिधित्व) को संकलित करना चाहिए, और बाद में तुरंत संकलित करना स्थगित करना चाहिए।
टेम्पलेट पर किए जाने वाले चेक, विशेष रूप से इसके त्वरित इंस्ट्रूमेंटेशन के बजाय, यह आवश्यक है कि कंपाइलर टेम्पलेट के कोड के व्याकरण को हल करने में सक्षम हो।
C ++ (और C) में, कोड के व्याकरण को हल करने के लिए, आपको कभी-कभी यह जानना होगा कि कुछ एक प्रकार है या नहीं। उदाहरण के लिए:
#if WANT_POINTER
typedef int A;
#else
int A;
#endif
static const int x = 2;
template <typename T> void foo() { A *x = 0; }
यदि A एक प्रकार है, जो एक पॉइंटर घोषित करता है (वैश्विक छाया के अलावा कोई प्रभाव नहीं है x
)। यदि A एक ऑब्जेक्ट है, तो यह गुणा है (और कुछ ऑपरेटर को अवैध रूप से ओवरलोड करने पर रोक लगाता है, जो एक प्रतिद्वंद्विता को असाइन करता है)। यदि यह गलत है, तो इस त्रुटि का चरण 1 में निदान किया जाना चाहिए , यह मानक द्वारा टेम्पलेट में एक त्रुटि होने के लिए परिभाषित किया गया है , न कि इसके कुछ विशेष तात्कालिकता में। भले ही टेम्प्लेट को कभी भी त्वरित नहीं किया जाता है, अगर ए int
तो एक उपरोक्त कोड बीमार है और इसका निदान किया जाना चाहिए, जैसे कि अगर यह बिल्कुल foo
टेम्पलेट नहीं था, लेकिन एक सादे कार्य।
अब, मानक का कहना है कि वे नाम जो टेम्प्लेट पैरामीटर पर निर्भर नहीं हैं , उन्हें चरण 1 में फिर से शुरू करने योग्य होना चाहिए। A
यहाँ एक निर्भर नाम नहीं है, यह एक ही चीज़ को संदर्भित करता है, भले ही प्रकार का हो T
। इसलिए इसे चरण 1 में ढूंढने और जांचने के लिए टेम्पलेट को परिभाषित करने से पहले परिभाषित किया जाना चाहिए।
T::A
एक ऐसा नाम होगा जो T पर निर्भर करता है। हम संभवतः चरण 1 में नहीं जान सकते कि यह एक प्रकार है या नहीं। प्रकार जो अंततः T
एक तात्कालिकता के रूप में उपयोग किया जाएगा काफी संभावना अभी भी परिभाषित नहीं किया गया है, और यहां तक कि अगर हम यह नहीं जानते थे कि कौन सा प्रकार (ओं) को हमारे टेम्पलेट पैरामीटर के रूप में उपयोग किया जाएगा। लेकिन हमें व्याकरण को हल करना होगा ताकि हमारे अनमोल चरण 1 की जांच बीमार लोगों के लिए हो सके। अतः मानक पर निर्भर नामों के लिए एक नियम है - संकलक को यह मान लेना चाहिए कि वे गैर-प्रकार के हैं, जब तक typename
कि यह निर्दिष्ट करने के लिए योग्य नहीं है कि वे टाइप हैं, या कुछ असंदिग्ध संदर्भों में उपयोग किए जाते हैं । उदाहरण के लिए template <typename T> struct Foo : T::A {};
,T::A
बेस क्लास के रूप में उपयोग किया जाता है और इसलिए यह एक प्रकार से स्पष्ट रूप से है। यदि Foo
किसी प्रकार के साथ तात्कालिकता है जिसमें एक डेटा सदस्य हैA
नेस्टेड प्रकार ए के बजाय, यह कोड में एक त्रुटि है तात्कालिकता (चरण 2), न कि टेम्पलेट (चरण 2) में त्रुटि।
लेकिन आश्रित आधार वर्ग के साथ वर्ग टेम्पलेट के बारे में क्या?
template <typename T>
struct Foo : Bar<T> {
Foo() { A *x = 0; }
};
A आश्रित नाम है या नहीं? बेस क्लास के साथ, बेस क्लास में कोई भी नाम दिखाई दे सकता है। इसलिए हम कह सकते हैं कि A एक आश्रित नाम है, और इसे एक गैर-प्रकार के रूप में मानते हैं। इसका अवांछनीय प्रभाव यह होगा कि फू में हर नाम निर्भर है, और इसलिए फू (बिल्ट-इन प्रकारों को छोड़कर) में उपयोग किए जाने वाले प्रत्येक प्रकार को योग्य बनाना होगा। फू के अंदर, आपको लिखना होगा:
typename std::string s = "hello, world";
क्योंकि std::string
एक आश्रित नाम होगा, और इसलिए एक गैर-प्रकार माना जाता है जब तक कि अन्यथा निर्दिष्ट न हो। आउच!
आपके पसंदीदा कोड ( return x;
) की अनुमति देने के साथ एक दूसरी समस्या यह है कि भले ही Bar
पहले परिभाषित किया गया हो Foo
, और x
उस परिभाषा में कोई सदस्य नहीं है, कोई बाद में Bar
किसी प्रकार के लिए एक विशेषज्ञता को परिभाषित कर सकता है Baz
, Bar<Baz>
जिसमें डेटा सदस्य हो x
, और फिर तुरंत Foo<Baz>
। तो उस तात्कालिकता में, आपका खाका वैश्विक वापस करने के बजाय डेटा सदस्य को वापस कर देगा x
। या इसके विपरीत अगर बेस टेम्प्लेट की परिभाषा Bar
थी x
, तो वे इसके बिना एक विशेषज्ञता को परिभाषित कर सकते थे, और आपका टेम्प्लेट x
वापस लौटने के लिए एक वैश्विक की तलाश करेगा Foo<Baz>
। मुझे लगता है कि यह आपके लिए समस्या के रूप में आश्चर्यजनक और चिंताजनक था, लेकिन यह चुपचाप है आश्चर्यजनक, के रूप में एक आश्चर्यजनक त्रुटि फेंकने का विरोध किया।
इन समस्याओं से बचने के लिए, प्रभाव में मानक का कहना है कि वर्ग टेम्पलेट्स के आश्रित आधार वर्गों को केवल तब तक खोज के लिए नहीं माना जाता है जब तक कि स्पष्ट रूप से अनुरोध नहीं किया जाता है। यह सब कुछ निर्भर होने से रोकता है क्योंकि यह एक आश्रित आधार में पाया जा सकता है। इसका अवांछनीय प्रभाव भी है जो आप देख रहे हैं - आपको बेस क्लास से सामान प्राप्त करना होगा या यह नहीं मिला है। A
आश्रित बनाने के तीन सामान्य तरीके हैं :
using Bar<T>::A;
कक्षा में - A
अब कुछ में संदर्भित करता है Bar<T>
, इसलिए निर्भर है।
Bar<T>::A *x = 0;
उपयोग के बिंदु पर - फिर, A
निश्चित रूप से अंदर है Bar<T>
। यह गुणा typename
का उपयोग नहीं किया गया था, इसलिए संभवतः एक बुरा उदाहरण है, लेकिन हमें यह पता लगाने के लिए कि जब तक operator*(Bar<T>::A, x)
कोई प्रतिफल नहीं मिल जाता है, तब तक इंतजार करना होगा । कौन जानता है, शायद यह करता है ...
this->A;
उपयोग के बिंदु पर - A
एक सदस्य है, इसलिए यदि यह अंदर नहीं है Foo
, तो यह आधार वर्ग में होना चाहिए, फिर से मानक कहता है कि यह निर्भर करता है।
दो-चरण का संकलन फिडली और कठिन है, और आपके कोड में अतिरिक्त वर्बेज के लिए कुछ आश्चर्यजनक आवश्यकताओं का परिचय देता है। लेकिन लोकतंत्र की तरह यह संभवत: अन्य सभी के अलावा चीजों को करने का सबसे खराब तरीका है।
आप यथोचित तर्क दे सकते हैं कि आपके उदाहरण में, return x;
यह समझ में नहीं आता है कि x
क्या आधार वर्ग में एक नेस्टेड प्रकार है, इसलिए भाषा को (ए) कहना चाहिए कि यह एक आश्रित नाम है और (2) इसे एक गैर-प्रकार के रूप में मानते हैं, और आपका कोड बिना काम करेगा this->
। एक हद तक आप समस्या के समाधान से संपार्श्विक क्षति का शिकार होते हैं जो आपके मामले में लागू नहीं होता है, लेकिन अभी भी आपके आधार वर्ग का मुद्दा संभावित रूप से आपके नीचे उन नामों को प्रस्तुत कर रहा है जो छाया ग्लोबल्स हैं, या आपके द्वारा सोचे गए नाम नहीं हैं। उनके पास था, और एक वैश्विक बजाय पाया जा रहा है।
आप संभवतः यह भी तर्क दे सकते हैं कि डिफ़ॉल्ट निर्भर नामों के लिए विपरीत होना चाहिए (मान लें कि जब तक किसी वस्तु को निर्दिष्ट नहीं किया जाता है तब तक टाइप करें), या यह कि डिफ़ॉल्ट अधिक संदर्भ संवेदनशील होना चाहिए (में std::string s = "";
, std::string
एक प्रकार के रूप में पढ़ा जा सकता है क्योंकि कुछ भी व्याकरणिक नहीं बनाता है) भावना, भले ही std::string *s = 0;
अस्पष्ट है)। फिर, मुझे नहीं पता कि नियम कैसे सहमत हुए। मेरा अनुमान है कि पाठ के उन पृष्ठों की संख्या की आवश्यकता होगी, जिनके लिए बहुत से विशिष्ट नियमों को बनाने के खिलाफ कम किया गया है, जिनके लिए संदर्भ एक प्रकार का है और जो एक गैर-प्रकार है।