2011-09-05 16 views
6

मैं अपनी कक्षा का उपयोग अपने मूल वर्ग में से किसी एक के टेम्पलेट पैरामीटर के रूप में कर रहा हूं, और यह कि अभिभावक वर्ग इसे टेम्पलेट तर्क में उपयोग करता है (हालांकि आकार())।आधार वर्ग के टेम्पलेट पैरामीटर के रूप में बाल वर्ग का उपयोग करना और घोंसला वाले नाम के रूप में

और संकलक मुझे देता है:

त्रुटि: अधूरा प्रकार 'Invoker :: workerClass {उर्फ MyClass}' नेस्टेड नाम विनिर्देशक में इस्तेमाल

फिर भी वर्ग अच्छी तरह से फाइल में परिभाषित किया गया है । मुझे लगता है कि ऐसा इसलिए है क्योंकि बेस क्लास 'तत्कालता के पल में बाल वर्ग को तत्काल नहीं किया जाता है, फिर भी इस तरह की चीज CRTP के साथ होती है और इसमें कोई समस्या नहीं है।

टेम्पलेट तर्क में बाल वर्ग का उपयोग करने का कारण एक अलग फ़ंक्शन कॉल करना है यदि बच्चे वर्ग के पास एक विशिष्ट कार्य है, या नहीं है।

यहाँ के परीक्षण के लिए एक न्यूनतम कोड है

/* Structure similar to boost's enable if, to use 
    SFINAE */ 
template <int X=0, class U = void> 
struct test { 
    typedef U type; 
}; 

enum Commands { 
    Swim, 
    Fly 
}; 

/* Structure used for template overloading, 
    as no partial function template specialization available */ 
template<Commands T> 
struct Param { 

}; 

template <class T> 
class Invoker 
{ 
public: 
    typedef T workerClass; 

    workerClass *wc() { 
     return static_cast<workerClass*>(this); 
    } 

    template <Commands command> 
    void invoke() { 
     invoke2(Param<command>()); 
    } 

    /* If the child class has those functions, call them */ 
    /* Needs template paramter Y to apply SFINAE */ 
    template<class Y=int> 
    typename test<sizeof(Y)+sizeof(decltype(&workerClass::fly))>::type 
    invoke2(Param<Fly>) { 
     wc()->fly(); 
    } 

    template<class Y=int> 
    typename test<sizeof(Y)+sizeof(decltype(&workerClass::swim))>::type 
    invoke2(Param<Swim>) { 
     wc()->shoot(); 
    } 

    template<Commands command> 
    void invoke2(Param<command>) { 
     /* Default action */ 
     printf("Default handler for command %d\n", command); 
    } 
}; 

template <class T, class Inv = Invoker<T> > 
class BaseClass : public Inv 
{ 
public: 
    template<Commands command> 
    void invoke() { 
     Inv::template invoke<command>(); 
    } 
}; 

class MyClass : public BaseClass<MyClass> 
{ 
public: 
    void swim() { 
     printf("Swimming like a fish!\n"); 
    } 

    /* void fly(); */ 
}; 


void testing() { 
    MyClass foo; 
    foo.invoke<Fly>(); /* No 'void fly()' in MyClass, calls the default handler */ 
    foo.invoke<Swim>(); /* Should print the swimming message */ 
} 

त्रुटि पंक्ति होता है:

typename test<sizeof(Y)+sizeof(decltype(&workerClass::fly))>::type 

तो, वहाँ किसी भी संकलक है कि इस का समर्थन करता है, या इस पर स्पष्ट रूप के रूप में मानक द्वारा निर्दिष्ट किया जाता टेम्पलेट्स का अमान्य उपयोग? क्या मुझे ऐसा करने के तरीके को बदलना है और चारों ओर एक रास्ता खोजना है? सीआरटीपी मुझे आशा दे रहा है कि कोड वैध हो सकता है, लेकिन मुझे यकीन नहीं है।

यदि यह वास्तव में संभव नहीं है, तो क्यों, और सीआरटीपी क्यों काम करता है?

+4

ठीक है, समस्या स्पष्ट है कम से कम: आप एक आत्म निर्देशात्मक पुनरावर्ती परिभाषा कर रहे हैं: 'BaseClass ' आवश्यकता पूरा '' MyClass' decltype की वजह से 'अभिव्यक्ति, लेकिन' MyClass' को बेस क्लास के रूप में एक पूर्ण 'बेस क्लास ' की आवश्यकता है। –

+1

आपको संकेत की एक अतिरिक्त परत के पीछे विशेषज्ञता को छिपाने की जरूरत है ताकि बेस बेस परिभाषा के हिस्से के रूप में उनका मूल्यांकन नहीं किया जा सके। उन्हें एक निजी नेस्टेड प्रकार में रखें और सभी ठीक काम करेंगे। – ildjarn

+0

सीआरटीपी इस कारण से मुश्किल है, आप जिस समस्या का सामना कर रहे हैं वह समझने में आसान है: आधार टेम्पलेट को कक्षा घोषणा के हिस्से के रूप में तत्काल किया जाता है, एक बिंदु पर जहां यह अभी तक पूरा नहीं हुआ है (लगता है कि यह प्रकार अभी भी इस बात पर निर्भर करता है कि आधार टेम्पलेट जोड़ सकता है, इसलिए कोई तरीका नहीं है कि संकलक यह जान सके कि व्युत्पन्न प्रकार पहले बेस टेम्पलेट को संसाधित किए बिना कैसा होगा - आधार यह है कि आधार सदस्य गुणों को जोड़ सकता है जो आकार बदलते हैं, या आभासी सदस्य फ़ंक्शन जो बदल सकते हैं व्युत्पन्न प्रकार में फ़ंक्शन घोषणाओं का अर्थ) –

उत्तर

2

समाधान, जैसा कि ildjarn ने इंगित किया था, संकेत का एक और स्तर जोड़ने के लिए।

परीक्षण समारोह को बदलने के प्रकारों को स्वीकार करने द्वारा किया जाता है यही कारण है कि:

template <typename X, class U = void> 
struct test { 
    typedef U type; 
}; 

और फिर बजाय से यह निर्दिष्ट करने के लिए, एक टेम्पलेट पैरामीटर के रूप में बच्चे को कक्षा पास कर जाना:

template<class Y=workerClass> 
    typename test<decltype(&Y::fly)>::type 
    invoke2(Param<Fly>) { 
     wc()->fly(); 
    } 

    template<class Y=workerClass> 
    typename test<decltype(&Y::swim)>::type 
    invoke2(Param<Swim>) { 
     wc()->swim(); 
    } 

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

टेम्पलेट अब भी और अधिक पठनीय है। और नमूना कोड अब बस ठीक काम करता है:

class MyClass : public BaseClass<MyClass> 
{ 
public: 
    void swim() { 
     printf("Swimming like a fish!\n"); 
    } 

    /* void fly(); */ 
}; 


void testing() { 
    MyClass foo; 
    foo.invoke<Fly>(); /* No 'void fly()' in MyClass, calls the default handler */ 
    foo.invoke<Swim>(); /* Should print the swimming message */ 
} 
+0

यह बहुत दिलचस्प है। क्या कोई इसके लिए एक दिलचस्प उपयोग केस प्रदान कर सकता है? – Zhro

+0

@Zhro जिस तरह से मैंने इसका उपयोग किया है वह वर्चुअल टेबल द्वारा प्रेरित (नगण्य) ओवरहेड के बिना वर्चुअल फ़ंक्शन जैसे सिस्टम बनाना है। – coyotte508

संबंधित मुद्दे