2013-01-31 54 views
7

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

#include <iostream> 


template<typename some_type> 
struct some_meta_class; 

template<> 
struct some_meta_class<int> 
{ 
    typedef void type; 
}; 



template<typename some_type> 
struct return_type 
{ 
    typedef typename some_meta_class<some_type>::type test; 

    typedef void type; 
}; 



template<typename type> 
typename return_type<type>::type foo(type & in) 
{ 
    std::cout << "non-const" << std::endl; 
} 

template<typename type> 
void foo(type const & in) 
{ 
    std::cout << "const" << std::endl; 
} 


int main() 
{ 
    int i; 

    int const & ciref = i; 
    foo(ciref); 
} 

मैं एक गैर स्थिरांक संस्करण और foo के लिए एक स्थिरांक संस्करण लेकिन दुर्भाग्य से इस कोड को लागू करने के लिए बजना 3.0 और जीसीसी 4.6.3 पर संकलन नहीं होगा की कोशिश की: निम्नलिखित स्रोत कोड एक न्यूनतम उदाहरण घटना है।

main.cpp:18:22: error: implicit instantiation of undefined template 'some_meta_class'

तो किसी कारण से संकलक एक कॉन्स इंट-रेफरेंस के लिए foo के गैर-कॉन्स संस्करण का उपयोग करना चाहता है। यह स्पष्ट रूप से ऊपर की त्रुटि की ओर जाता है क्योंकि some_meta_class के लिए कोई कार्यान्वयन नहीं है। अजीब बात यह है कि यदि आप निम्न परिवर्तन से कोई एक कार्य, कोड अच्छी तरह से संकलित, और काम करता है:

  • टिप्पणी हटाएं/गैर-स्थिरांक संस्करण को निकालने
  • uncomemnt/return_type की typedef :: परीक्षण
  • को दूर

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

लेकिन उपर्युक्त उदाहरण इस तरह के अजीब व्यवहार में क्यों परिणाम देता है? संकलक गैर-कॉन्स संस्करण का उपयोग क्यों नहीं करना चाहता जहां कॉन्स्ट संस्करण मान्य है और बेहतर मिलान करता है?

+0

वर्तमान में यह संभव वैध अधिभारों की एक सूची उत्पन्न करने के चरण में है, और चूंकि यह वापसी_type को ठीक से ठीक नहीं कर सकता है, यह वहां से बाहर निकलता है (मेरा मतलब देखने के लिए return_type में परीक्षण के टाइपपीफ पर टिप्पणी करें) – PlasmaHH

उत्तर

15

कारण फ़ंक्शन कॉल रिज़ॉल्यूशन का प्रदर्शन किया गया है, साथ ही टेम्पलेट तर्क कटौती और प्रतिस्थापन के साथ।

  1. सबसे पहले, नाम देखने किया जाता है। यह आपको मेलिंग नाम foo() के साथ दो फ़ंक्शन देता है।

  2. दूसरे, प्रकार कटौती किया जाता है: उससे मिलते-जुलते नाम के साथ टेम्पलेट कार्यों में से प्रत्येक के लिए, संकलक समारोह टेम्पलेट तर्क जो एक व्यवहार्य मिलान उत्पन्न होता अनुमान की कोशिश करता है। इस चरण में आपको जो त्रुटि मिलती है।

  3. तीसरा, ओवरलोड रिज़ॉल्यूशन गेम में प्रवेश करता है। के बाद यह केवल है, कटौती का प्रदर्शन किया गया है और कॉल को हल करने के लिए व्यवहार्य कार्यों के हस्ताक्षर निर्धारित किए गए हैं, जो समझ में आता है: संकलक केवल आपके फ़ंक्शन कॉल को सार्थक रूप से हल कर सकता है जब उसे सभी के सटीक हस्ताक्षर मिलते हैं उम्मीदवार।

सच है कि आप क्योंकि संकलक कॉल के समाधान के लिए एक सबसे व्यवहार्य उम्मीदवार के रूप में यह चुनता है (चरण 3 होगा) गैर स्थिरांक अधिभार से संबंधित कोई त्रुटि नहीं है मिलता है कि है, लेकिन क्योंकि संकलक का उत्पादन एक त्रुटि है, जबकि उसके हस्ताक्षर निर्धारित करने के लिए कदम के दौरान अपनी वापसी प्रकार instantiating 2.

यह पूरी तरह से स्पष्ट नहीं क्यों कि यह एक त्रुटि में परिणाम है हालांकि, क्योंकि एक उम्मीद कर सकते हैं कि SFINAE लागू होता है (प्रतिस्थापन विफलता एक नहीं है त्रुटि)।इस स्पष्ट करने के लिए, हम एक सरल उदाहरण पर विचार हो सकता है: इस उदाहरण में

template<typename T> struct X { }; 

template<typename T> typename X<T>::type f(T&) { } // 1 
template<typename T> void f(T const&) { }   // 2 

int main() 
{ 
    int const i = 0; 
    f(i); // Selects overload 2 
} 

, SFINAE लागू होता है: चरण 2 के दौरान, संकलक उपरोक्त दो भार के से प्रत्येक के लिए T निकालना होगा, और अपने हस्ताक्षर निर्धारित करने के लिए प्रयास करें। अधिभार 1 के मामले में, परिणामस्वरूप प्रतिस्थापन विफलता: X<const int>type (X में कोई परिभाषित नहीं करता है)। हालांकि, SFINAE के कारण, कंपाइलर बस इसे छोड़ देता है और पाया जाता है कि अधिभार 2 एक व्यवहार्य मिलान है। इस प्रकार, यह इसे चुनता है।

चलो अब एक तरीका है कि दर्पण अपने उदाहरण में थोड़ा उदाहरण बदल डालते हैं:

template<typename T> struct X { }; 

template<typename Y> 
struct R { typedef typename X<Y>::type type; }; 

// Notice the small change from X<T> into R<T>! 
template<typename T> typename R<T>::type f(T&) { } // 1 
template<typename T> void f(T const&) { }   // 2 

int main() 
{ 
    int const i = 0; 
    f(i); // ERROR! Cannot instantiate R<int const> 
} 

क्या बदल गया है कि अधिभार 1 अब कोई रिटर्न X<T>::type, बल्कि R<T>::type है। यह इसलिए भी इसे समान ही परिणाम चाहते हैं, इसलिए हीR में typedef घोषणा की वजह से X<T>::type के रूप में बारी में है। हालांकि, इस मामले में आपको एक संकलन त्रुटि मिलती है। क्यूं कर?

If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. [...] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.

जाहिर है, दूसरे उदाहरण (और साथ ही तुम्हारा) तो SFINAE नहीं है एक नेस्टेड संदर्भ में कोई त्रुटि उत्पन्न करता है,:

स्टैंडर्ड जवाब (पैराग्राफ 14.8.3/8) है लागू करें। मेरा मानना ​​है कि यह आपके प्रश्न का उत्तर देता है।

वैसे, यह सूचना के लिए दिलचस्प है, कि इस सी ++ 03 है, जो अधिक आम तौर पर कहा गया है के बाद से बदल गया है (पैराग्राफ 14.8.2/2):

[...] If a substitution in a template parameter or in the function type of the function template results in an invalid type, type deduction fails. [...]

आप उत्सुक हैं कारणों क्यों चीजें बदल गई, के बारे में this paper आप यह अनुमान लगा सकता है।

+0

अच्छा विश्लेषण जो जोड़े जासूस काम और व्यावहारिक भाषा lawyering। –

+0

@ होंक: धन्यवाद :-) –

+0

यह स्पष्ट करता है, बहुत बहुत धन्यवाद! –