( मेरे C ++ 11 उत्तर के लिए यहां भी देखें )
सी ++ प्रोग्राम को पार्स करने के लिए, कंपाइलर को यह जानना होगा कि कुछ नाम टाइप हैं या नहीं। निम्न उदाहरण प्रदर्शित करता है कि:
t * f;
इसे कैसे पार्स किया जाना चाहिए? कई भाषाओं के लिए एक संकलक को पार्स करने के लिए एक नाम का अर्थ जानने की आवश्यकता नहीं होती है और मूल रूप से पता होता है कि कोड की एक पंक्ति क्या कार्रवाई करती है। C ++ में, हालांकि उपरोक्त t
साधनों के आधार पर बहुत भिन्न व्याख्याएं उत्पन्न कर सकते हैं । यदि यह एक प्रकार है, तो यह एक सूचक की घोषणा होगी f
। हालाँकि यदि यह एक प्रकार नहीं है, तो यह एक गुणा होगा। तो C ++ मानक पैराग्राफ में कहता है (3/7):
कुछ नाम प्रकार या टेम्पलेट को दर्शाते हैं। सामान्य तौर पर, जब भी किसी नाम का सामना किया जाता है, तो यह निर्धारित करना आवश्यक है कि क्या उस नाम को उस प्रोग्राम को पार्स करना जारी रखने से पहले इन संस्थाओं में से किसी एक को दर्शाता है। यह निर्धारित करने वाली प्रक्रिया को नाम लुकअप कहा जाता है।
कंपाइलर को कैसे पता चलेगा कि कोई नाम t::x
किसको संदर्भित करता है, यदि t
वह टेम्पलेट प्रकार के पैरामीटर को संदर्भित करता है? x
एक स्थैतिक int डेटा सदस्य हो सकता है जिसे गुणा किया जा सकता है या समान रूप से एक नेस्टेड वर्ग या टाइपडेफ हो सकता है जो एक घोषणा के लिए तैयार हो सकता है। यदि किसी नाम में यह गुण है - जिसे तब तक देखा नहीं जा सकता जब तक कि वास्तविक टेम्पलेट तर्क ज्ञात नहीं हो जाते - तब इसे एक आश्रित नाम कहा जाता है (यह टेम्पलेट मापदंडों पर "निर्भर करता है")।
आप तब तक प्रतीक्षा करने की अनुशंसा कर सकते हैं जब तक कि उपयोगकर्ता टेम्पलेट को तत्काल न दे दे:
आइए तब तक प्रतीक्षा करें जब तक कि उपयोगकर्ता टेम्पलेट को त्वरित नहीं करता है, और फिर बाद में इसका वास्तविक अर्थ पता करता है t::x * f;
।
यह काम करेगा और वास्तव में मानक द्वारा एक संभावित कार्यान्वयन दृष्टिकोण के रूप में अनुमति दी जाती है। ये कंपाइलर मूल रूप से टेम्प्लेट के टेक्स्ट को एक आंतरिक बफर में कॉपी करते हैं, और केवल जब इंस्टेंटेशन की आवश्यकता होती है, तो वे टेम्पलेट को पार्स करते हैं और संभवतः परिभाषा में त्रुटियों का पता लगाते हैं। लेकिन टेम्पलेट के लेखक द्वारा की गई त्रुटियों के साथ टेम्पलेट के उपयोगकर्ताओं (गरीब सहकर्मियों!) को परेशान करने के बजाय, अन्य कार्यान्वयन जल्द से जल्द टेम्प्लेट की जांच करने और परिभाषा में त्रुटियों को जल्द से जल्द चुनने के लिए चुनते हैं।
इसलिए संकलक को यह बताने का एक तरीका होना चाहिए कि कुछ नाम प्रकार हैं और कुछ निश्चित नाम नहीं हैं।
"टाइपनेम" कीवर्ड
इसका उत्तर है: हम तय करते हैं कि कंपाइलर को यह कैसे पार्स करना चाहिए। यदि t::x
एक आश्रित नाम है, तो हमें typename
इसे एक निश्चित तरीके से पार्सल करने के लिए संकलक को बताना होगा। मानक कहता है (14.6 / 2):
एक टेम्पलेट घोषणा या परिभाषा में उपयोग किया जाने वाला नाम और जो एक टेम्पलेट-पैरामीटर पर निर्भर है, यह माना जाता है कि एक प्रकार का नाम नहीं है जब तक कि लागू नाम लुकअप को एक प्रकार का नाम नहीं मिलता है या नाम कीवर्ड टाइपनेम द्वारा योग्य है।
ऐसे कई नाम हैं जिनके लिए typename
आवश्यक नहीं है, क्योंकि कंपाइलर, टेम्प्लेट की परिभाषा में लागू नाम लुकअप के साथ, यह पता लगा सकता है कि एक निर्माण को कैसे पार्स किया जाए - उदाहरण के लिए T *f;
, जब T
एक प्रकार का टेम्प्लेट पैरामीटर है। लेकिन t::x * f;
एक घोषणा के लिए, इसे लिखा जाना चाहिए typename t::x *f;
। यदि आप कीवर्ड को छोड़ देते हैं और नाम एक गैर-प्रकार का लिया जाता है, लेकिन जब तात्कालिकता पता चलता है कि यह एक प्रकार को दर्शाता है, तो सामान्य त्रुटि संदेश कंपाइलर द्वारा उत्सर्जित होते हैं। कभी-कभी, परिणामी समय पर त्रुटि दी जाती है:
// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;
सिंटैक्स typename
केवल योग्य नामों से पहले ही अनुमति देता है - यह वहाँ के रूप में लिया जाता है कि अयोग्य नाम हमेशा ऐसा करने के लिए टाइप करने के लिए संदर्भित करने के लिए जाना जाता है।
समान गोटे उन नामों के लिए मौजूद हैं जो टेम्पलेट को दर्शाते हैं, जैसा कि परिचयात्मक पाठ द्वारा संकेत दिया गया है।
"टेम्पलेट" कीवर्ड
ऊपर दिए गए प्रारंभिक उद्धरण को याद रखें और मानक को टेम्प्लेट के लिए विशेष हैंडलिंग की आवश्यकता कैसे होती है? आइए निम्नलिखित निर्दोष दिखने वाले उदाहरण लेते हैं:
boost::function< int() > f;
यह एक मानवीय पाठक को स्पष्ट लग सकता है। संकलक के लिए ऐसा नहीं है। निम्नलिखित मनमानी परिभाषा की कल्पना करें boost::function
और f
:
namespace boost { int function = 0; }
int main() {
int f = 0;
boost::function< int() > f;
}
यह वास्तव में एक वैध अभिव्यक्ति है ! यह boost::function
शून्य से तुलना करने के लिए कम-से-कम ऑपरेटर का उपयोग करता है ( int()
), और फिर परिणाम के bool
खिलाफ तुलना करने के लिए अधिक से अधिक ऑपरेटर का उपयोग करता है f
। हालांकि जैसा कि आप अच्छी तरह से जानते हैं, boost::function
वास्तविक जीवन में एक टेम्पलेट है, इसलिए कंपाइलर जानता है (14.2 / 3)
नाम लुकअप (3.4) के बाद पता चलता है कि एक नाम एक टेम्पलेट-नाम है, यदि इस नाम का अनुसरण <होता है, तो <हमेशा टेम्पलेट-तर्क-सूची की शुरुआत के रूप में लिया जाता है और कभी भी निम्न के नाम के रूप में नहीं- ऑपरेटर की तुलना में।
अब हम वापस उसी समस्या के साथ हैं typename
। क्या होगा अगर हम अभी तक यह नहीं जान सकते हैं कि कोड को पार्स करते समय नाम एक टेम्पलेट है या नहीं? template
जैसा कि निर्दिष्ट किया गया है, हमें तुरंत टेम्पलेट नाम से पहले सम्मिलित करना होगा 14.2/4
। ऐसा दिखता है:
t::template f<int>(); // call a function template
टेम्पलेट नाम के बाद ही एक नहीं हो सकता है ::
लेकिन यह भी एक के बाद ->
या .
एक वर्ग के सदस्य पहुंच में। आपको वहां कीवर्ड भी डालना होगा:
this->template f<int>(); // call a function template
निर्भरता
उन लोगों के लिए जिनके पास शेल्फ पर मोटी मानक किताबें हैं और यह जानना चाहते हैं कि मैं वास्तव में किस बारे में बात कर रहा था, मैं इस बारे में थोड़ी बात करूंगा कि यह मानक में कैसे निर्दिष्ट किया गया है।
टेम्प्लेट घोषणाओं में, कुछ कंस्ट्रक्ट के अलग-अलग अर्थ होते हैं, जो इस बात पर निर्भर करता है कि आप टेम्प्लेट को तत्काल करने के लिए कौन से टेम्प्लेट का उपयोग करते हैं: एक्सप्रेशन के विभिन्न प्रकार या मान हो सकते हैं, चर अलग-अलग प्रकार के हो सकते हैं या फ़ंक्शन कॉल अलग-अलग फ़ंक्शन को कॉल कर सकते हैं। इस तरह के निर्माणों को आम तौर पर टेम्पलेट मापदंडों पर निर्भर कहा जाता है ।
मानक नियमों को ठीक से परिभाषित करता है कि कोई निर्माण निर्भर है या नहीं। यह उन्हें तार्किक रूप से अलग-अलग समूहों में अलग करता है: एक प्रकार पकड़ता है, दूसरा भावों को पकड़ता है। भाव उनके मूल्य और / या उनके प्रकार पर निर्भर हो सकते हैं। तो हमारे पास विशिष्ट उदाहरण हैं,
- आश्रित प्रकार (जैसे: एक प्रकार का टेम्पलेट पैरामीटर
T
)
- मूल्य-निर्भर अभिव्यक्ति (जैसे: एक गैर-प्रकार टेम्पलेट पैरामीटर
N
)
- टाइप-डिपेंडेंट एक्सप्रेशंस (जैसे: एक टाइप टू टाइप टेम्पलेट पैरामीटर
(T)0
)
अधिकांश नियम सहज हैं और पुनरावर्ती रूप से निर्मित हैं: उदाहरण के लिए, एक प्रकार का निर्माण T[N]
एक आश्रित प्रकार N
है जो एक मूल्य-निर्भर अभिव्यक्ति है या T
एक निर्भर प्रकार है। इस का विवरण अनुभागों में (14.6.2/1
) निर्भर प्रकारों के लिए, (14.6.2.2)
प्रकार-निर्भर अभिव्यक्तियों के लिए और (14.6.2.3)
मूल्य-निर्भर अभिव्यक्तियों के लिए पढ़ा जा सकता है ।
आश्रित नाम
मानक थोड़ा स्पष्ट है कि वास्तव में एक आश्रित नाम क्या है । एक साधारण पढ़ने पर (आप जानते हैं, कम से कम आश्चर्य का सिद्धांत), यह सब निर्भर नाम के रूप में परिभाषित करता है नीचे दिए गए फ़ंक्शन नामों के लिए विशेष मामला है। लेकिन चूंकि स्पष्ट रूप T::x
से तात्कालिक संदर्भ में भी देखने की जरूरत है, इसलिए इसे एक आश्रित नाम भी होना चाहिए (सौभाग्य से, मध्य C ++ 14 के रूप में समिति ने इस भ्रामक परिभाषा को कैसे ठीक किया जाए, इस पर गौर करना शुरू कर दिया है)।
इस समस्या से बचने के लिए, मैंने मानक पाठ की एक सरल व्याख्या का सहारा लिया है। सभी प्रकार के निर्माण जो आश्रित प्रकार या भावों को दर्शाते हैं, उनमें से एक सबसेट नामों का प्रतिनिधित्व करता है। वे नाम इसलिए "निर्भर नाम" हैं। एक नाम अलग-अलग रूप ले सकता है - मानक कहता है:
एक नाम एक पहचानकर्ता (2.11), ऑपरेटर-फ़ंक्शन-आईडी (13.5), रूपांतरण-फ़ंक्शन-आईडी (12.3.2), या टेम्पलेट-आईडी (14.2) का उपयोग होता है जो एक इकाई या लेबल को दर्शाता है (6.6.4,) 6.1)
एक पहचानकर्ता वर्णों / अंकों का एक सादा क्रम है, जबकि अगले दो operator +
और operator type
रूप हैं। पिछले रूप है template-name <argument list>
। ये सभी नाम हैं, और मानक में पारंपरिक उपयोग से, एक नाम में क्वालिफायर भी शामिल हो सकते हैं जो कहते हैं कि नाम या वर्ग को किस नाम से देखा जाना चाहिए।
एक मूल्य पर निर्भर अभिव्यक्ति 1 + N
एक नाम नहीं है, लेकिन N
है। सभी आश्रित निर्माणों के सबसेट जो कि नाम हैं, आश्रित नाम कहलाते हैं । फ़ंक्शंस के नाम, हालांकि, टेम्प्लेट के विभिन्न इंस्टेंसेस में अलग-अलग अर्थ हो सकते हैं, लेकिन दुर्भाग्य से इस सामान्य नियम से पकड़े नहीं जाते हैं।
आश्रित फ़ंक्शन नाम
मुख्य रूप से इस लेख की चिंता नहीं है, लेकिन अभी भी ध्यान देने योग्य है: फ़ंक्शन नाम एक अपवाद है जिसे अलग से नियंत्रित किया जाता है। एक पहचानकर्ता फ़ंक्शन नाम अपने आप पर निर्भर नहीं होता है, लेकिन कॉल में उपयोग किए जाने वाले प्रकार पर निर्भर तर्क अभिव्यक्तियों द्वारा। उदाहरण में f((T)0)
, f
एक आश्रित नाम है। मानक में, यह निर्दिष्ट किया गया है (14.6.2/1)
।
अतिरिक्त नोट्स और उदाहरण
पर्याप्त मामलों में हमें typename
और दोनों की आवश्यकता है template
। आपका कोड निम्नलिखित जैसा दिखना चाहिए
template <typename T, typename Tail>
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
typedef typename Tail::template inUnion<U> dummy;
};
// ...
};
कीवर्ड template
को हमेशा नाम के अंतिम भाग में प्रदर्शित नहीं होना पड़ता है। यह एक वर्ग के नाम से पहले बीच में दिखाई दे सकता है जिसका उपयोग एक गुंजाइश के रूप में किया जाता है, जैसे निम्न उदाहरण में
typename t::template iterator<int>::value_type v;
कुछ मामलों में, कीवर्ड नीचे दिए गए अनुसार मना किए जाते हैं
एक आश्रित आधार वर्ग के नाम पर आपको लिखने की अनुमति नहीं है typename
। यह माना जाता है कि दिया गया नाम एक वर्ग प्रकार का नाम है। यह बेस-क्लास सूची और कंस्ट्रक्टर इनिशियलाइज़र सूची दोनों नामों के लिए सही है:
template <typename T>
struct derive_from_Has_type : /* typename */ SomeBase<T>::type
{ };
template
अंतिम- उपयोग के बाद का उपयोग करना संभव नहीं है ::
, और C ++ समिति ने कहा कि समाधान पर काम न करें।
template <typename T>
struct derive_from_Has_type : SomeBase<T> {
using SomeBase<T>::template type; // error
using typename SomeBase<T>::type; // typename *is* allowed
};