यह टेम्प्लेट फ़ंक्शन अपेक्षा के अनुरूप व्यवहार क्यों नहीं करता है?


23

मैं टेम्पलेट कार्यों के बारे में पढ़ रहा था और इस समस्या से उलझन में था:

#include <iostream>

void f(int) {
    std::cout << "f(int)\n";
}

template<typename T>
void g(T val) {
    std::cout << typeid(val).name() << "  ";
    f(val);
}

void f(double) {
    std::cout << "f(double)\n";
}

template void g<double>(double);

int main() {
    f(1.0); // f(double)
    f(1);   // f(int)
    g(1.0); // d  f(int), this is surprising
    g(1);   // i  f(int)
}

यदि मैं नहीं लिखता तो परिणाम वही हैं template void g<double>(double);

मुझे लगता है कि के g<double>बाद तुरंत किया जाना चाहिए f(double), और इसलिए कॉल fमें gकॉल करना चाहिए f(double)। हैरानी की बात है, यह अभी भी अंदर बुलाता f(int)है g<double>। क्या कोई मुझे यह समझने में मदद कर सकता है?


जवाब पढ़ने के बाद, मुझे पता चला कि वास्तव में मेरा भ्रम क्या है।

यहाँ एक अद्यतन उदाहरण है। यह ज्यादातर अपरिवर्तित है सिवाय इसके कि मैंने इसके लिए एक विशेषज्ञता जोड़ी g<double>:

#include <iostream>

void f(int){cout << "f(int)" << endl;}

template<typename T>
void g(T val)
{
    cout << typeid(val).name() << "  ";
    f(val);
}

void f(double){cout << "f(double)" << endl;}

//Now use user specialization to replace
//template void g<double>(double);

template<>
void g<double>(double val)
{
    cout << typeid(val).name() << "  ";
    f(val);
}

int main() {
    f(1.0); // f(double)
    f(1);  // f(int)
    g(1.0); // now d  f(double)
    g(1);  // i  f(int)
}

उपयोगकर्ता विशेषज्ञता के साथ, g(1.0)जैसा मैं अपेक्षित था , वैसा ही व्यवहार करता है।

कंपाइलर को स्वचालित रूप g<double>से एक ही स्थान पर (या इसके बाद भी main(), जैसा कि C ++ प्रोग्रामिंग लैंग्वेज , 4 वें संस्करण की धारा 26.3.3 में वर्णित है) के लिए यह तुरंत नहीं करना चाहिए ?


3
आखिरी कॉल, मेरे लिए g(1)देता i f(int)है। आपने लिखा d f(double)। क्या यह एक टाइपो था?
HTNW

हाँ। माफ़ करना। नवीनीकृत
झोंगकी चेंग

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

जवाबों:


12

नाम fएक आश्रित नाम है (यह Tतर्क के माध्यम से निर्भर करता है val) और इसे दो चरणों में हल किया जाएगा :

  1. गैर-एडीएल लुकअप फ़ंक्शन की घोषणाओं की जांच करता है ... जो कि टेम्प्लेट परिभाषा परिभाषा से दिखाई देते हैं ।
  2. ADL फ़ंक्शन घोषणाओं की जांच करता है ... जो कि टेम्प्लेट परिभाषा परिभाषा या टेम्प्लेट इंस्टेंटेशन संदर्भ से दिखाई देते हैं ।

void f(double)टेम्पलेट परिभाषा संदर्भ से दिखाई नहीं देता है, और ADL इसे या तो नहीं ढूंढेगा, क्योंकि

मौलिक प्रकार के तर्कों के लिए, नामस्थान और वर्गों का संबद्ध सेट खाली है


हम आपके उदाहरण को थोड़ा संशोधित कर सकते हैं:

struct Int {};
struct Double : Int {};

void f(Int) { 
    std::cout << "f(Int)";
}

template<typename T>
void g(T val) {
    std::cout << typeid(val).name() << ' ';
    f(val);
    // (f)(val);
}

void f(Double) { 
    std::cout << "f(Double)";
}

int main() {
    g(Double{});
}

अब ADL void f(Double)दूसरे चरण में मिलेगा , और आउटपुट होगा 6Double f(Double)। हम ADL को लिखने (f)(val)(या ::f(val)) के बजाय अक्षम कर सकते हैं f(val)। तब आउटपुट 6Double f(Int)आपके उदाहरण के साथ अनुबंध में होगा ।


आपका बहुत बहुत धन्यवाद। मुझे आश्चर्य हो रहा है कि कोड में g <double> के लिए तात्कालिकता कहाँ है। क्या यह मुख्य से ठीक पहले () है। यदि हां, तो क्या तत्काल जी <डबल> परिभाषा को एफ (इंट) और एफ (डबल) दोनों को देखने में सक्षम नहीं होना चाहिए, और अंत में एफ (डबल) चुनना चाहिए?
झोंगकी चेंग

@ZhongqiCheng चरण 1 पर केवल टेम्पलेट परिभाषा संदर्भ पर विचार किया जाएगा, और उस संदर्भ void f(double)से दृश्यमान नहीं है - यह संदर्भ आपके घोषणा से पहले समाप्त होता है। चरण 2 पर एडीएल को कुछ भी नहीं मिलेगा, इसलिए टेम्पलेट तात्कालिकता संदर्भ यहां कोई भूमिका नहीं निभाता है।
Evg

@ZhongqiCheng, आपके संपादन में आपने एक परिभाषा प्रस्तुत की है void f(double), इसलिए यह फ़ंक्शन इससे दिखाई देता है। अब fआश्रित नाम नहीं है। f(val);की परिभाषा के बाद घोषित किए जाने के लिए अगर कोई बेहतर मैच होता g<double>, तो वह भी नहीं मिलता। "आगे देखने" का एकमात्र तरीका एडीएल (या कुछ पुराने संकलक हैं जो दो-चरण लुकअप को सही तरीके से लागू नहीं करते हैं)।
Evg

यहाँ आपके उत्तर की मेरी समझ है। मुझे यह मान लेना चाहिए कि फंक्शन टेम्प्लेट (g <int> और g <double>) टेम्प्लेट की परिभाषा के तुरंत बाद तैयार किए गए हैं। इसलिए यह f (डबल) नहीं देखेगा। क्या ये सही है। बहुत बहुत धन्यवाद।
झोंगझी चेंग

@ZhongqiCheng, तुरंत पहले ठीक किया गया main()। वे नहीं देखेंगे f(double), क्योंकि जब तात्कालिकता होती है, तब तक बहुत देर हो चुकी होती है: चरणबद्ध तरीके से देखने का काम पहले ही किया जा चुका है और यह नहीं मिला है f(double)
Evg

6

समस्या को f(double)उस बिंदु पर घोषित नहीं किया गया है जहां आप इसे कहते हैं; यदि आप इसके घोषणा पत्र को सामने रखते हैं template g, तो यह बुलाया जाएगा।

संपादित करें: कोई मैनुअल इंस्टेंटेशन का उपयोग क्यों करेगा?

(मैं केवल फंक्शन टेम्प्लेट्स के बारे में बात करूँगा, क्लास टेम्प्लेट्स के लिए भी समसामयिक तर्क रखता है।) मुख्य उपयोग संकलन समय को कम करने और / या उपयोगकर्ताओं से टेम्प्लेट के कोड को छिपाने के लिए है।

C ++ प्रोग्राम को 2 चरणों में बायनेरिज़ में बनाया गया है: संकलन और लिंकिंग। फ़ंक्शन के संकलन के लिए केवल फ़ंक्शन के हेडर को सफल करने के लिए कॉल की आवश्यकता होती है। सफल होने के लिए लिंकिंग के लिए, फ़ंक्शन के संकलित निकाय वाली ऑब्जेक्ट फ़ाइल की आवश्यकता है।

अब जब कंपाइलर किसी टेम्प्लेटेड फंक्शन की कॉल देखता है , तो वह क्या करता है यह इस बात पर निर्भर करता है कि वह टेम्प्लेट की बॉडी को जानता है या केवल हेडर को। यदि यह केवल हेडर को देखता है तो यह वही कार्य करता है जैसे कि फ़ंक्शन को समाप्त नहीं किया गया था: ऑब्जेक्ट फ़ाइल के लिए लिंकर के लिए कॉल के बारे में जानकारी डालता है। लेकिन अगर यह टेम्प्लेट के शरीर को भी देखता है तो यह दूसरी बात भी करता है: यह शरीर के समुचित उदाहरण को तुरंत तैयार करता है, इस शरीर को संकलित करता है और इसे ऑब्जेक्ट फ़ाइल में भी डालता है।

यदि कई स्रोत फाइलें टेम्प्लेटेड फ़ंक्शन के समान उदाहरण को कॉल करती हैं, तो उनकी प्रत्येक ऑब्जेक्ट फ़ाइलों में फ़ंक्शन के उदाहरण का संकलित संस्करण होगा। (लिंकर को इस बारे में पता है और सभी कॉल्स को एक ही संकलित फ़ंक्शन में हल करता है, इसलिए प्रोग्राम / लाइब्रेरी के अंतिम बाइनरी में केवल एक ही होगा।) हालांकि प्रत्येक स्रोत फ़ाइलों को संकलित करने के लिए फ़ंक्शन को त्वरित करना था और। संकलित, जिसमें समय लगा।

यदि लिंक का कार्य एक वस्तु फ़ाइल में है तो यह लिंक करने वाले के लिए काम करने के लिए पर्याप्त है। स्रोत फ़ाइल में टेम्प्लेट को मैन्युअल रूप से तुरंत करने के लिए कंपाइलर को फ़ंक्शन फ़ाइल के शरीर को स्रोत फ़ाइल की ऑब्जेक्ट फ़ाइल में डालने का एक तरीका है। (यह थोड़े है जैसे कि फ़ंक्शन को कॉल किया गया था, लेकिन तात्कालिकता उस स्थान पर लिखा गया है जहां फ़ंक्शन कॉल अमान्य होगा।) जब यह किया जाता है, तो आपके फ़ंक्शन को कॉल करने वाली सभी फ़ाइलों को केवल फ़ंक्शन के हेडर को जानने के लिए संकलित किया जा सकता है, इस प्रकार। समय बचाने के लिए यह प्रत्येक कॉल के साथ फ़ंक्शन के शरीर को त्वरित और संकलित करने में लगेगा।

दूसरा कारण (कार्यान्वयन छिपाना) अब समझ में आ सकता है। यदि कोई लाइब्रेरी लेखक चाहता है कि उसके टेम्पलेट फ़ंक्शन के उपयोगकर्ता फ़ंक्शन का उपयोग करने में सक्षम हों, तो वह आमतौर पर उन्हें टेम्पलेट का कोड देता है, इसलिए वे इसे स्वयं संकलित कर सकते हैं। यदि वह टेम्प्लेट के स्रोत कोड को गुप्त रखना चाहती है, तो वह उस कोड को मैन्युअल रूप से इंस्टैंट कर सकती है, जिसका उपयोग वह लाइब्रेरी बनाने के लिए करती है और उपयोगकर्ताओं को ऑब्जेक्ट संस्करण इस प्रकार स्रोत के बजाय प्राप्त करने के लिए देती है।

क्या इसका कोई मतलब है?


यदि आप लेखक के पहले कोड में प्रस्तुत तात्कालिकता और संपादित करने के बाद लेखक के दूसरे कोड में विशेषज्ञता के बीच अंतर को समझा सकते हैं, तो मैं आभारी हूं। मैंने विशेषज्ञता और तात्कालिकता और पुस्तकों के बारे में कई बार crereference की साइट पढ़ी है, लेकिन मुझे समझ नहीं आया। धन्यवाद
देव

@Dev: कृपया अपने प्रश्न को थोड़ा और निर्दिष्ट करें, मुझे यकीन नहीं है कि क्या उत्तर देना है। मूल रूप से इस मामले में अंतर यह है कि जब विशेषज्ञता मौजूद होती है तो संकलक इसका उपयोग करता है, जबकि जब यह मौजूद नहीं होता है, तो संकलक टेम्पलेट लेता है, इसका एक उदाहरण उत्पन्न करता है और इस उत्पन्न उदाहरण का उपयोग करता है। विशेषज्ञता के ऊपर कोड में और टेम्पलेट का उदाहरण समान कोड तक ले जाता है।
AshleyWilkes

मेरा प्रश्न कोड के उस भाग पर सटीक रूप से संक्षिप्त है: "टेम्पलेट शून्य जी <डबल> (डबल);" इसे प्रोग्रामिंग टेम्प्लेट में इंस्टेंटेशन का नाम दिया गया है, यदि आप जानते हैं कि। विशेषज्ञता थोड़ी अलग है, क्योंकि इसे दूसरे कोड की तरह घोषित किया जाता है, जिसे लेखक ने "टेम्पलेट <> void g <double> (double val) {cout << typeid (val) .name () <<" "; f" भेजा है। Val);} "क्या आप मुझे अंतर समझा सकते हैं?
देव

@ क्या मैंने पहले से ही ऐसा करने की कोशिश की: संकलक एक विशेषज्ञता का उपयोग करता है अगर यह कर सकता है; यदि यह विशेषज्ञता नहीं देख सकता (जैसे कि कोई नहीं है) तो कंपाइलर टेम्पलेट का एक उदाहरण बनाता है और उस उदाहरण का उपयोग करता है। टेम्पलेट और विशेषज्ञता दोनों से ऊपर कोड में एक ही परिणाम होता है, इसलिए केवल एक ही अंतर होता है कि संकलक उस परिणाम को प्राप्त करने के लिए क्या करता है। अन्य मामलों में विशेषज्ञता में कोई भी कार्यान्वयन हो सकता है, इसमें टेम्पलेट के साथ (लेकिन विधि शीर्षलेख के लिए) कुछ भी सामान्य नहीं है। स्पष्ट?
AshleyWilkes

1
template void g<double>(double);इसलिए मैनुअल इन्स्टेन्शियशन (नोट कहा जाता है templateकोई कोण कोष्ठक के साथ, वाक्य रचना की एक ख़ास विशेषता है कि); जो संकलक को विधि का एक उदाहरण बनाने के लिए कहता है। यहां इसका बहुत कम प्रभाव पड़ता है, अगर यह नहीं होता, तो संकलक उस जगह पर उदाहरण उत्पन्न करता है जिसे उदाहरण कहा जाता है। मैनुअल तात्कालिकता का उपयोग शायद ही कभी किया जाता है, मैं कहूंगा कि आप इस बात की पुष्टि करने के बाद कि आप इसका उपयोग क्यों करना चाहते हैं, अब बात स्पष्ट हो गई है :-)
एशलेविलेक्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.