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




templates typename (4)

टेम्पलेट्स में, मुझे निर्भर नामों पर 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<U> एक प्रकार का नाम है और कोई फ़ंक्शन नहीं है?


प्रस्तावना

यह पोस्ट लीटब के पोस्ट के लिए एक आसान-पढ़ने के विकल्प के रूप में है।

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

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

typename और template घोषित करते समय परिस्थितियों में प्रयोग योग्य है।

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

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

यह पोस्ट आश्रित नामों और दो कीवर्ड के बीच संबंधों के लिए एक स्पष्टीकरण प्रदान करेगा।

एक स्निपेट 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> ();


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

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

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


तर्कसंगत

सी ++ मानक कम से कम इस मामले में हमारी सुरक्षा और कल्याण की परवाह करता है।

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

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


निस्संदेह नामों को कैसे संभालें ?

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

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

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> , और; पर निर्भर करता है;
    • ( एफ ) के अंत में "टाइप" नेस्टेडट्रेट पर निर्भर करता है, जो कुछ SomeTrait<T> , और;
  • जी )
    • "डेटा" , जो सदस्य-फ़ंक्शन टेम्पलेट की तरह दिखता है, अप्रत्यक्ष रूप से एक आश्रित-नाम है क्योंकि foo के प्रकार कुछ SomeTrait<T> के तत्काल पर निर्भर करता है।

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

समाधान

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

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 लागू typename

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


क्या मैं किसी भी नाम के मुकाबले कुंजीशब्दों को रोक सकता हूं?

" क्या मैं सिर्फ किसी नाम के सामने typename और template चिपका सकता हूं? मैं उस संदर्भ के बारे में चिंता नहीं करना चाहता जिसमें वे दिखाई देते हैं ... " - Some C++ Developer

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

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
     };
    

सी ++ 11

मुसीबत

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

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 बाद के विशेषज्ञता से प्रभावित नहीं हो सकता है!)।

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

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

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

यदि क्वालीफायर वर्तमान typename सदस्य है तो कीवर्ड typename और template अब और आवश्यक नहीं हैं। याद रखने के लिए यहां एक महत्वपूर्ण बिंदु यह है कि 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 तुरंत चालू किया D::f (भले ही इसे परिभाषा समय पर पहले से ही इसका अर्थ मिल जाए)। जब अब लुकअप परिणाम अलग-अलग होता है या परिणाम अस्पष्टता में होता है, तो कार्यक्रम खराब हो जाता है और निदान दिया जाना चाहिए। कल्पना करें कि क्या होता है यदि हमने C को इस तरह परिभाषित किया C

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

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

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

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

कल्पना करें कि क्या होता है यदि हमारे पास उपर्युक्त परिभाषित A क्लास टेम्पलेट के भीतर सदस्य फ़ंक्शन h था

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

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

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

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

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

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


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

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 बजाय इसे ठीक से पार्स करेगा।


typedef typename Tail::inUnion<U> dummy;

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

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 };
};

पीएस: Boost::Variant पर एक नज़र डालें

पीएस 2: typelists पर एक नज़र typelists , विशेष रूप से एंड्री अलेक्जेंड्रेस्कू की पुस्तक: मॉडर्न सी ++ डिज़ाइन में





dependent-name