मुझे "टेम्पलेट" और "टाइपनेम" कीवर्ड कहां और क्यों डालने हैं?


1125

टेम्पलेट में, जहां और कारण है कि मैं डाल करने की क्या ज़रूरत है typenameऔर templateनिर्भर नाम पर?
वैसे भी वास्तव में निर्भर नाम क्या हैं?

मेरे पास निम्नलिखित कोड हैं:

template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        // Q: where to add typename/template here?
        typedef Tail::inUnion<U> dummy; 
    };
    template< > struct inUnion<T> {
    };
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
    // ...
    template<typename U> struct inUnion {
        char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U
    };
    template< > struct inUnion<T> {
    };
};

मुझे जो समस्या है वह typedef Tail::inUnion<U> dummyलाइन में है। मैं काफी हद तक निश्चित हूं कि inUnionएक आश्रित नाम है, और वीसी ++ इस पर विचार करने में काफी सही है।
मुझे यह भी पता है कि मुझे templateसंकलक को यह बताने में सक्षम होना चाहिए कि इनयूनिशन एक टेम्पलेट-आईडी है। लेकिन वास्तव में कहाँ? और फिर यह मान लेना चाहिए कि inUnion एक क्लास टेम्प्लेट है, अर्थात inUnion<U>एक प्रकार का नाम और एक फ़ंक्शन नहीं है?


1
कष्टप्रद प्रश्न: बढ़ावा क्यों नहीं दिया गया?
असफ लावी

58
राजनीतिक संवेदनशीलता, पोर्टेबिलिटी।
MSalters

5
मैंने आपका वास्तविक प्रश्न ("टेम्पलेट / टाइपनेम कहां रखा जाए?") को अंतिम प्रश्न और कोड को शुरुआत में रखकर बेहतर तरीके से खड़ा किया और एक 1024x स्क्रीन को फिट करने के लिए कोड को क्षैतिज रूप से छोटा कर दिया।
जोहान्स स्काउब -

7
"आश्रित नाम" को शीर्षक से हटा दिया क्योंकि ऐसा प्रतीत होता है कि "टाइपनेम" और "टेम्पलेट" के बारे में आश्चर्य करने वाले अधिकांश लोग नहीं जानते कि "निर्भर नाम" क्या हैं। उन्हें इस तरह से कम भ्रमित होना चाहिए।
जोहान्स शाउब -

2
@MSalters: बूस्ट काफी पोर्टेबल है। मैं कहूंगा कि केवल राजनीति ही सामान्य कारण है, क्योंकि बढ़ावा अक्सर मिलता-जुलता है। केवल एक ही अच्छा कारण मुझे पता है कि बढ़ा हुआ समय है। अन्यथा यह पहिया को सुदृढ़ करने वाले हजारों डॉलर खोने के बारे में है।
v.oddou

जवाबों:


1161

( मेरे 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
     };

22
यह उत्तर मेरे पहले पूछे जाने वाले प्रश्न प्रविष्टि से कॉपी किया गया था, जिसे मैंने हटा दिया, क्योंकि मैंने पाया कि मुझे नए "छद्म प्रश्न" बनाने के बजाय मौजूदा समान प्रश्नों का बेहतर उपयोग करना चाहिए। धन्यवाद @Prasoon पर जाएं , जिन्होंने अंतिम भाग के विचारों को संपादित किया (उन मामलों में जहां टाइपनेम / टेम्पलेट निषिद्ध है)।
जोहान्स शहाब -

1
क्या मुझे इस सिंटैक्स का उपयोग करना चाहिए? यह-> टेम्पलेट f <int> (); मुझे यह त्रुटि 'टेम्प्लेट' के रूप में मिलती है (डिस्बिग्यूएटर के रूप में) केवल टेम्प्लेट के भीतर ही अनुमति दी जाती है, लेकिन टेम्प्लेट कीवर्ड के बिना यह ठीक काम करता है।
बालकी

1
मैंने आज एक समान प्रश्न पूछा, जो जल्द ही डुप्लिकेट के रूप में चिह्नित किया गया था: stackoverflow.com/questions/27923722/… । मुझे एक नया बनाने के बजाय इस प्रश्न को पुनर्जीवित करने का निर्देश दिया गया था। मुझे कहना चाहिए कि मैं उन पर डुप्लिकेट होने पर सहमत नहीं हूं, लेकिन मैं कौन हूं, ठीक है? तो, क्या कोई कारण है जो typenameतब भी लागू किया जाता है जब वाक्यविन्यास इस बिंदु पर टाइप-नामों के अलावा कोई वैकल्पिक व्याख्या की अनुमति नहीं देता है?
जोरेंहिट

1
@ पाब्लो आपको कुछ याद नहीं आ रहा है। लेकिन फिर भी आवश्यकता है कि पूर्ण पंक्ति अब अस्पष्ट नहीं होगी, भले ही उसे लिखने की आवश्यकता हो।
जोहान्स शाउब -

1
@ पाब्लो का उद्देश्य भाषा और संकलक को सरल रखना है। अधिक स्थितियों को स्वचालित रूप से चीजों का पता लगाने की अनुमति देने के लिए प्रस्ताव हैं, ताकि आपको कम बार कीवर्ड की आवश्यकता हो। ध्यान रखें कि आपके उदाहरण में, टोकन है अस्पष्ट और के बाद ही आप को देखा है ">" डबल के बाद, आप इसे एक टेम्पलेट कोण कोष्ठक के रूप में स्पष्ट करने कर सकते हैं। अधिक जानकारी के लिए, मैं पूछने के लिए गलत व्यक्ति हूं, क्योंकि मुझे C ++ कंपाइलर के पार्सर को लागू करने का कोई अनुभव नहीं है।
जोहान्स शाउब -

135

सी ++ 11

मुसीबत

जबकि C ++ 03 में नियम जब आपको आवश्यकता होती है typenameऔर templateकाफी हद तक उचित होते हैं, इसके निर्माण का एक कष्टप्रद नुकसान है

template<typename T>
struct A {
  typedef int result_type;

  void f() {
    // error, "this" is dependent, "template" keyword needed
    this->g<float>();

    // OK
    g<float>();

    // error, "A<T>" is dependent, "typename" keyword needed
    A<T>::result_type n1;

    // OK
    result_type n2; 
  }

  template<typename U>
  void g();
};

जैसा कि देखा जा सकता है, हमें डिसएम्बिगेशन कीवर्ड की आवश्यकता होती है, भले ही कंपाइलर पूरी तरह से खुद को समझ सकता है जो A::result_typeकेवल int(और इसलिए यह एक प्रकार है) this->gहो सकता है , और केवल gबाद में घोषित सदस्य टेम्पलेट हो सकता है (भले ही Aस्पष्ट रूप से कहीं विशेष रूप से विशिष्ट हो, उस टेम्पलेट के भीतर कोड को प्रभावित न करें, इसलिए इसका अर्थ बाद के विशेषज्ञता से प्रभावित नहीं हो सकता है A!)।

वर्तमान तात्कालिकता

स्थिति को सुधारने के लिए, C ++ 11 में भाषा ट्रैक तब आती है जब एक प्रकार संलग्नक टेम्पलेट को संदर्भित करता है। कि पता करने के लिए, प्रकार का नाम की एक निश्चित रूप है, जो अपने स्वयं के नाम है (ऊपर में, का उपयोग करके गठन किया गया है चाहिए A, A<T>, ::A<T>)। इस तरह के नाम से संदर्भित एक प्रकार को वर्तमान तात्कालिकता के रूप में जाना जाता है । कई प्रकार हो सकते हैं जो सभी वर्तमान तात्कालिकता हैं यदि जिस प्रकार से नाम बनता है वह एक सदस्य / नेस्टेड क्लास (तब, A::NestedClassऔर Aदोनों वर्तमान इंस्टेंटिएशन हैं)।

इस धारणा के आधार पर, भाषा कहती है कि CurrentInstantiation::Foo, Fooऔर CurrentInstantiationTyped->Foo(जैसे कि A *a = this; a->Foo) सभी वर्तमान तात्कालिकता के सदस्य हैं यदि वे एक वर्ग के सदस्य पाए जाते हैं जो कि वर्तमान तात्कालिकता या उसके गैर-निर्भर आधार वर्गों में से एक है (केवल करके नाम तुरंत खोज)।

यदि वर्तमान योग्यता तत्काल सदस्य है तो कीवर्ड typenameऔर templateअब इसकी आवश्यकता नहीं है। यहाँ एक Keypoint याद करने के लिए वह यह है कि A<T>है अभी भी (सभी के बाद एक प्रकार पर निर्भर नाम Tभी निर्भर प्रकार है)। लेकिन A<T>::result_typeएक प्रकार के रूप में जाना जाता है - संकलक "जादुई रूप से" इस तरह के आश्रित प्रकारों में यह पता लगाने के लिए देखेंगे।

struct B {
  typedef int result_type;
};

template<typename T>
struct C { }; // could be specialized!

template<typename T>
struct D : B, C<T> {
  void f() {
    // OK, member of current instantiation!
    // A::result_type is not dependent: int
    D::result_type r1;

    // error, not a member of the current instantiation
    D::questionable_type r2;

    // OK for now - relying on C<T> to provide it
    // But not a member of the current instantiation
    typename D::questionable_type r3;        
  }
};

यह प्रभावशाली है, लेकिन क्या हम बेहतर कर सकते हैं? भाषा आगे भी बढ़ जाती है और आवश्यकता होती है कि D::result_typeइंस्टेंटिअटिंग करते समय एक कार्यान्वयन फिर से दिखता है D::f(भले ही यह परिभाषा समय पर पहले से ही पाया गया हो)। जब अब लुकअप परिणाम भिन्न होता है या अस्पष्टता में परिणाम होता है, तो प्रोग्राम बीमार हो जाता है और एक निदान दिया जाना चाहिए। सोचिए कि अगर हम Cइस तरह से परिभाषित करें तो क्या होगा

template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

तत्काल करते समय त्रुटि को पकड़ने के लिए एक कंपाइलर की आवश्यकता होती है D<int>::f। तो आप दो दुनियाओं में से सर्वश्रेष्ठ प्राप्त करते हैं: "विलंबित" लुकअप आपकी रक्षा करता है यदि आप आश्रित आधार वर्गों के साथ परेशानी में पड़ सकते हैं, और "तत्काल" लुकअप जो आपको typenameऔर से मुक्त करता है template

अज्ञात विशेषज्ञता

कोड में D, नाम typename D::questionable_typeवर्तमान तात्कालिकता का सदस्य नहीं है। इसके बजाय भाषा इसे एक अज्ञात विशेषज्ञता के सदस्य के रूप में चिह्नित करती है । विशेष रूप से, यह मामला हमेशा के लिए जब आप कर रहे हैं DependentTypeName::Fooया DependentTypedName->Fooऔर या तो निर्भर प्रकार है नहीं वर्तमान इन्स्टेन्शियशन (इस स्थिति में संकलक हार और कहते हैं कि "हम बाद में कैसा दिखाई देगा कर सकते हैं Foo) या यह है वर्तमान इन्स्टेन्शियशन और इसमें नाम या इसके गैर-निर्भर आधार वर्ग नहीं पाए गए और आश्रित आधार वर्ग भी हैं।

कल्पना करें कि hऊपर परिभाषित Aवर्ग टेम्पलेट के भीतर यदि हमारे पास कोई सदस्य कार्य है तो क्या होगा

void h() {
  typename A<T>::questionable_type x;
}

C ++ 03 में, भाषा ने इस त्रुटि को पकड़ने की अनुमति दी क्योंकि वहाँ कभी A<T>::hभी तात्कालिकता (आप जो भी तर्क देते हैं T) के लिए एक वैध तरीका नहीं हो सकता है । C ++ 11 में, भाषा में अब इस नियम को लागू करने के लिए कंपाइलरों के लिए अधिक कारण बताने के लिए एक और जाँच है। के बाद से Aकोई निर्भर आधार वर्ग है, और Aकोई भी सदस्य वाणी questionable_type, नाम A<T>::questionable_typeहै वर्तमान इन्स्टेन्शियशन के एक सदस्य है और न हीएक अज्ञात विशेषज्ञता का सदस्य। उस स्थिति में, ऐसा कोई तरीका नहीं होना चाहिए कि वह कोड तात्कालिकता के समय को वैध रूप से संकलित कर सके, इसलिए भाषा एक ऐसे नाम को मना करती है जहां क्वालीफायर वर्तमान तात्कालिकता है जो न तो किसी अज्ञात विशेषज्ञता का सदस्य है और न ही वर्तमान तात्कालिकता का सदस्य है (हालाँकि , यह उल्लंघन अभी भी निदान की आवश्यकता नहीं है)।

उदाहरण और सामान्य ज्ञान

आप इस उत्तर पर इस ज्ञान को आज़मा सकते हैं और देख सकते हैं कि क्या उपरोक्त परिभाषाएँ वास्तविक दुनिया के उदाहरण पर आपके लिए समझ में आती हैं (वे उस उत्तर में थोड़े कम विस्तृत दोहराए जाते हैं)।

C ++ 11 नियम निम्नलिखित वैध C ++ 03 कोड को बीमार बनाते हैं (जो C ++ समिति द्वारा अभिप्रेत नहीं था, लेकिन शायद तय नहीं किया जाएगा)

struct B { void f(); };
struct A : virtual B { void f(); };

template<typename T>
struct C : virtual B, T {
  void g() { this->f(); }
};

int main() { 
  C<A> c; c.g(); 
}

यह वैध सी ++ 03 कोड बाध्य होगा this->fकरने के लिए A::fइन्स्टेन्शियशन समय में और सब कुछ ठीक है। C ++ 11 हालांकि तुरंत इसे बांधता है B::fऔर तत्काल जांच करते समय डबल-चेक की आवश्यकता होती है, यह देखते हुए कि क्या लुकअप अभी भी मेल खाता है। हालांकि, जब इंस्टेंटिंग होता है C<A>::g, तो डोमिनेंस नियम लागू होता है और A::fइसके बजाय लुकअप मिलेगा ।


fyi - इस उत्तर को यहाँ संदर्भित किया गया है: stackoverflow.com/questions/56411114/… इस उत्तर के अधिकांश कोड विभिन्न संकलक पर संकलित नहीं होते हैं।
एडम रैकिस

@AdamRackis मान रहा है कि 2013 के बाद से सी ++ कल्पना नहीं बदली है (जिस तारीख को मैंने यह उत्तर लिखा था), फिर जिन कंपाइलरों के साथ आपने अपना कोड आज़माया था, वे अभी तक इस C ++ 11 + -feature को लागू नहीं करते हैं।
जोहान्स स्काउब -

98

प्रस्तावना

इस पोस्ट का अभिप्राय लिटब पोस्ट के लिए एक आसान-से पढ़ा जाने वाला विकल्प है

अंतर्निहित उद्देश्य समान है; "कब?" और क्यों?" typenameऔर templateलागू किया जाना चाहिए।


का उद्देश्य क्या है typenameऔरtemplate ?

typename तथा template जब एक टैम्पलेट घोषित करने के अलावा अन्य परिस्थितियों में प्रयोग करने योग्य हैं।

C ++ में कुछ संदर्भ हैं जहां संकलक को स्पष्ट रूप से बताया जाना चाहिए कि किसी नाम का इलाज कैसे किया जाए, और इन सभी संदर्भों में एक बात समान है; वे कम से कम एक टेम्पलेट-पैरामीटर पर निर्भर करते हैं

हम ऐसे नामों का उल्लेख करते हैं, जहां व्याख्या में अस्पष्टता हो सकती है, जैसे; “ आश्रित नाम "।

यह पोस्ट रिश्ते के बीच स्पष्टीकरण की पेशकश करेगा आश्रित-नामों और दो खोजशब्दों के ।


एक स्निपेट 1000 से अधिक काम करता है

निम्नलिखित फ़ंक्शन-टेम्प्लेट में क्या चल रहा है, यह समझाने की कोशिश करें कि या तो अपने आप को, एक दोस्त, या शायद आपकी बिल्ली; चिह्नित किए गए कथन ( ) में क्या हो रहा है ?

template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }


यह जितना आसान लगता है उतना आसान नहीं हो सकता है, विशेष रूप से मूल्यांकन का परिणाम ( ) भारी निर्भर करता है रूप से टेम्पलेट-पैरामीटर के रूप में पारित की गई परिभाषा परT

अलग-अलग Tएस बहुत तेजी से शामिल शब्दार्थ को बदल सकते हैं।

struct X { typedef int       foo;       }; /* (C) --> */ f_tmpl<X> ();
struct Y { static  int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();


दो अलग-अलग परिदृश्य :

  • यदि हम फ़ंक्शन-टेम्पलेट को X के साथ टाइप करते हैं , तो ( C ) के रूप में , हमारे पास x नाम के एक पॉइंटर-इन इंट की घोषणा होगी , लेकिन;

  • यदि हम टाइप Y के साथ टेम्पलेट को त्वरित करते हैं , तो ( D ) के रूप में , ( A ) इसके बजाय एक अभिव्यक्ति से मिलकर बनेगा जो 123 के उत्पाद को पहले से घोषित चर x के साथ गुणा करता है ।



तर्कसंगत

C ++ मानक हमारी सुरक्षा और भलाई के बारे में परवाह करता है, कम से कम इस मामले में।

क्रियान्विति को आश्चर्यजनक रूप से पीड़ित होने से रोकने के लिए, मानक यह कहता है कि हम एक आश्रित-नाम की अस्पष्टता को स्पष्ट रूप से बताते हुए इरादे को स्पष्ट करते हैं, कहीं भी हम नाम को एक प्रकार-नाम , या एक टेम्पलेट के रूप में मानना चाहेंगे। आईडी

यदि कुछ भी नहीं कहा गया है, तो आश्रित-नाम को एक चर या एक फ़ंक्शन माना जाएगा।



कैसे विनम्र नाम दें ?

यदि यह एक हॉलीवुड फिल्म थी, तो आश्रित-नाम शरीर के संपर्क के माध्यम से फैलने वाली बीमारी होगी, तुरंत अपने मेजबान को भ्रमित करने के लिए प्रभावित करती है। भ्रम है कि, संभवतः, एक बीमार कायम perso-, erhm .. कार्यक्रम के लिए नेतृत्व कर सकते हैं।

एक आश्रित नाम है किसी भी नाम है जो सीधे या परोक्ष रूप से, एक पर निर्भर करता है टेम्पलेट पैरामीटर

template<class T> void g_tmpl () {
   SomeTrait<T>::type                   foo; // (E), ill-formed
   SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
   foo.data<int> ();                         // (G), ill-formed    
}

उपरोक्त स्निपेट में हमारे चार आश्रित नाम हैं:

  • )
    • "प्रकार" की तात्कालिकता पर निर्भर करता है SomeTrait<T>, जिसमें शामिल हैं T, और;
  • )
    • "NestedTrait" , जो एक टेम्पलेट-आईडी है , निर्भर करता है SomeTrait<T>, और;
    • ( F ) के अंत में "टाइप" NestedTrait पर निर्भर करता है , जो निर्भर करता है SomeTrait<T>, और;
  • जी )
    • "डेटा" , जो एक सदस्य-फ़ंक्शन टेम्पलेट की तरह दिखता है , अप्रत्यक्ष रूप से एक निर्भर नाम है क्योंकि फू का प्रकार तात्कालिकता पर निर्भर करता है SomeTrait<T>

यदि कंपाइलर आश्रित-नामों को चर / कार्यों के रूप में व्याख्या करेगा (जो पहले कहा गया है तो क्या होगा अगर हम स्पष्ट रूप से अन्यथा नहीं कहते हैं) तो न तो कथन ( ), ( एफ ) या ( जी ) मान्य है ।

समाधान

g_tmplएक वैध परिभाषा बनाने के लिए हमें संकलक को स्पष्ट रूप से बताना चाहिए कि हम किस प्रकार ( E ), एक टेम्पलेट-आईडी और एक प्रकार ( F ), और एक टेम्पलेट-आईडी इन ( G ) की अपेक्षा करते हैं ।

template<class T> void g_tmpl () {
   typename SomeTrait<T>::type foo;                            // (G), legal
   typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
   foo.template data<int> ();                                  // (I), legal
}

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

templateहालाँकि, इस संबंध में अलग है, क्योंकि इस तरह के निष्कर्ष पर आने का कोई तरीका नहीं है; "ओह, यह एक टेम्प्लेट है, तो इस दूसरी चीज़ का भी एक टेम्प्लेट होना चाहिए" । इसका मतलब है कि हम templateकिसी भी नाम के सामने सीधे आवेदन करते हैं जिसे हम इस तरह से व्यवहार करना चाहते हैं।



क्या मैं किसी भी नाम के सामने केवल आँकड़े देख सकता हूँ ?

" Can मैं सिर्फ छड़ी typenameऔर templateकिसी भी नाम के सामने मैं संदर्भ में वे दिखाई देते हैं के बारे में चिंता नहीं करना चाहता ...? " -Some C++ Developer

मानक के नियमों में कहा गया है कि जब तक आप एक योग्य-नाम ( K ) के साथ काम कर रहे हैं, तब तक आप कीवर्ड लागू कर सकते हैं , लेकिन यदि नाम योग्य नहीं है , तो आवेदन अ -गठित ( L ) है।

namespace N {
  template<class T>
  struct X { };
}

         N::         X<int> a; // ...  legal
typename N::template X<int> b; // (K), legal
typename template    X<int> c; // (L), ill-formed

नोट : लागू करना typenameया templateएक संदर्भ में जहां इसकी आवश्यकता नहीं है, अच्छा अभ्यास नहीं माना जाता है; सिर्फ इसलिए कि आप कुछ कर सकते हैं, इसका मतलब यह नहीं है कि आपको चाहिए।


इसके अतिरिक्त वहाँ संदर्भों कहाँ हो typenameऔर templateकर रहे हैं स्पष्ट रूप से अनुमति नहीं दी:

  • जब एक वर्ग विरासत के आधारों को निर्दिष्ट करता है

    एक व्युत्पन्न वर्ग के आधार-निर्दिष्ट-सूची में लिखे गए प्रत्येक नाम को पहले से ही एक प्रकार का नाम माना जाता है , स्पष्ट रूप से निर्दिष्ट typenameकरना बीमार और निरर्थक दोनों है।

                       // .------- the base-specifier-list
     template<class T> // v
     struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
       ...
     };


  • जब टेम्प्लेट-आईडी को किसी व्युत्पन्न वर्ग के उपयोग-निर्देश में संदर्भित किया जा रहा है

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };

20
typedef typename Tail::inUnion<U> dummy;

हालाँकि, मुझे यकीन नहीं है कि आप inUnion का कार्यान्वयन सही है। अगर मैं सही तरीके से समझूं, तो इस वर्ग को तत्काल नहीं माना जाता है, इसलिए "असफल" टैब कभी भी असफल नहीं होगा। शायद यह इंगित करना बेहतर होगा कि क्या प्रकार एक साधारण बूलियन मूल्य के साथ संघ में है या नहीं।

template <typename T, typename TypeList> struct Contains;

template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
    enum { result = Contains<T, Tail>::result };
};

template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
    enum { result = true };
};

template <typename T>
struct Contains<T, void>
{
    enum { result = false };
};

पुनश्च: बूस्ट पर एक नज़र है :: भिन्न

PS2: टाइपिस्टों पर एक नज़र डालें , विशेष रूप से आंद्रेई अलेक्जेंड्रेस्कु की पुस्तक: मॉडर्न सी ++ डिज़ाइन में


यदि आप उदाहरण के लिए यूनियन <फ्लोट, बूल> :: ऑपरेटर = (यू) को यू == इंट के साथ कॉल करने का प्रयास करते हैं, तो इन <u> तत्काल होगा। यह एक निजी सेट (U, inUnion <U> * = 0) कहता है।
MSALERS 10

और परिणाम के साथ काम = सही / गलत यह है कि मुझे बढ़ावा देने की आवश्यकता होगी :: enable_if <>, जो हमारे वर्तमान OSX टूलकिन के साथ असंगत है। अलग टेम्पलेट अभी भी एक अच्छा विचार है, हालांकि।
MSalters

ल्यूक का अर्थ है टाईपडेफ टेल :: इनयूनिशन <यू> डमी; लाइन। कि टेल को तुरंत चलेगा। लेकिन inUnion <U> नहीं। जब इसे इसकी पूरी परिभाषा की जरूरत होती है तो यह तुरंत हो जाता है। उदाहरण के लिए ऐसा होता है यदि आप आकार लेते हैं, या किसी सदस्य का उपयोग करते हैं (:: foo का उपयोग करके)। : @MSalters वैसे भी, आप एक और समस्या मिल गया है
litb - Johannes Schaub

-साइज़ोफ़ (U) कभी भी ऋणात्मक नहीं होता है :) क्योंकि size_t एक अहस्ताक्षरित पूर्णांक प्रकार है। आपको कुछ बहुत अधिक संख्या मिलेगी। आप शायद sizeof (U)> = 1 करना चाहते हैं? -1: 1 या समान :)
जोहान्स स्काउब -

मैं बस इसे अपरिभाषित छोड़ दूंगा और केवल इसे घोषित करूंगा: टेम्पलेट <टाइपनेम U> संरचना inUnion; तो यह निश्चित रूप से त्वरित नहीं किया जा सकता है। मुझे लगता है कि इसका साइज़ोफ के साथ होना, कंपाइलर को अनुमति देता है कि आप इसे एक त्रुटि भी दें, भले ही आप इसे तुरंत दें, क्योंकि यदि आपको पता है कि साइज़ोफ़ (यू) हमेशा> = 1 है और ...
जोहान्स स्काउब -

20

यह जवाब शीर्षक वाले प्रश्न का उत्तर (भाग) देने के लिए एक छोटा और मीठा होना है। यदि आप अधिक विवरण के साथ एक उत्तर चाहते हैं जो बताता है कि आपको उन्हें वहां क्यों रखना है, तो कृपया यहां जाएं


typenameकीवर्ड डालने के लिए सामान्य नियम ज्यादातर तब होता है जब आप टेम्पलेट पैरामीटर का उपयोग कर रहे होते हैं और typedefउदाहरण के लिए किसी नेस्टेड या उपयोग-उपनाम का उपयोग करना चाहते हैं :

template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

ध्यान दें कि यह मेटा फ़ंक्शंस या उन चीज़ों के लिए भी लागू होता है जो जेनेरिक टेम्पलेट पैरामीटर भी लेते हैं। हालाँकि, यदि प्रदान किया गया टेम्प्लेट पैरामीटर स्पष्ट प्रकार है तो आपको typenameउदाहरण के लिए निर्दिष्ट करने की आवश्यकता नहीं है :

template<typename T>
struct test {
    // typename required
    using type = typename std::conditional<true, const T&, T&&>::type;
    // no typename required
    using integer = std::conditional<true, int, float>::type;
};

templateक्वालीफायर को जोड़ने के सामान्य नियम ज्यादातर समान हैं सिवाय इसके कि वे आमतौर पर एक संरचना / वर्ग के टेम्पर्ड सदस्य कार्यों (स्थिर या अन्यथा) को शामिल करते हैं जो स्वयं उदाहरण के लिए टेम्पलेटेड है:

इस संरचना और कार्य को देखते हुए:

template<typename T>
struct test {
    template<typename U>
    void get() const {
        std::cout << "get\n";
    }
};

template<typename T>
void func(const test<T>& t) {
    t.get<int>(); // error
}

t.get<int>()फ़ंक्शन के अंदर से प्रवेश करने का प्रयास करने के परिणामस्वरूप त्रुटि होगी:

main.cpp:13:11: error: expected primary-expression before 'int'
     t.get<int>();
           ^
main.cpp:13:11: error: expected ';' before 'int'

इस प्रकार इस संदर्भ में आपको templateपहले से ही कीवर्ड की आवश्यकता होगी और इसे इस तरह से कॉल करना होगा:

t.template get<int>()

इस तरह संकलक इसके बजाय ठीक से पार्स करेगा t.get < int


2

मैं cplusplus.com से इसी तरह के प्रश्न शब्दशः JLBorges की उत्कृष्ट प्रतिक्रिया दे रहा हूं, क्योंकि यह इस विषय पर पढ़ी गई सबसे अधिक व्याख्या है।

एक टेम्पलेट में, जो हम लिखते हैं, दो प्रकार के नाम हैं जिनका उपयोग किया जा सकता है - निर्भर नाम और गैर-निर्भर नाम। एक आश्रित नाम एक ऐसा नाम है जो टेम्पलेट पैरामीटर पर निर्भर करता है; एक गैर-निर्भर नाम का एक ही अर्थ है कि चाहे जो भी हो, टेम्पलेट पैरामीटर क्या हैं।

उदाहरण के लिए:

template< typename T > void foo( T& x, std::string str, int count )
{
    // these names are looked up during the second phase
    // when foo is instantiated and the type T is known
    x.size(); // dependant name (non-type)
    T::instance_count ; // dependant name (non-type)
    typename T::iterator i ; // dependant name (type)

    // during the first phase, 
    // T::instance_count is treated as a non-type (this is the default)
    // the typename keyword specifies that T::iterator is to be treated as a type.

    // these names are looked up during the first phase
    std::string::size_type s ; // non-dependant name (type)
    std::string::npos ; // non-dependant name (non-type)
    str.empty() ; // non-dependant name (non-type)
    count ; // non-dependant name (non-type)
}

टेम्पलेट के प्रत्येक अलग-अलग तात्कालिकता के लिए एक आश्रित नाम जो संदर्भित करता है वह कुछ अलग हो सकता है। परिणामस्वरूप, C ++ टेम्प्लेट "दो-चरण नाम लुकअप" के अधीन हैं। जब किसी टेम्पलेट को शुरू में पार्स किया जाता है (किसी भी इंस्टेंटेशन से पहले) कंपाइलर गैर-निर्भर नामों को देखता है। जब टेम्पलेट का एक विशेष तात्कालिकता होता है, तब तक टेम्पलेट पैरामीटर ज्ञात हो जाते हैं, और कंपाइलर निर्भर नामों को देखता है।

पहले चरण के दौरान, पार्सर को यह जानना होगा कि क्या एक आश्रित नाम एक प्रकार का नाम है या एक गैर-प्रकार का नाम है। डिफ़ॉल्ट रूप से, एक आश्रित नाम को गैर-प्रकार का नाम माना जाता है। एक आश्रित नाम से पहले टाइपनेम का नाम निर्दिष्ट करता है कि यह एक प्रकार का नाम है।


सारांश

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.