c++ - टेम्पलेट तर्क प्रतिस्थापन का आदेश क्यों मायने रखता है?



templates c++11 (1)

सी ++ 11

14.8.2 - टेम्पलेट तर्क कटौती - [temp.deduct]

7 प्रतिस्थापन सभी प्रकारों और अभिव्यक्तियों में होता है जो फ़ंक्शन प्रकार और टेम्पलेट पैरामीटर घोषणाओं में उपयोग किए जाते हैं। अभिव्यक्तियों में न केवल निरंतर अभिव्यक्तियां होती हैं जो कि सरणी सीमाओं में दिखाई देती हैं या नॉनटाइप टेम्पलेट तर्कों के रूप में दिखाई देती हैं, लेकिन सामान्य अभिव्यक्ति (यानी गैर-निरंतर अभिव्यक्ति) sizeof , decltype , और अन्य संदर्भों के अंदर जो निरंतर अभिव्यक्तियों को अनुमति देती हैं।

सी ++ 14

14.8.2 - टेम्पलेट तर्क कटौती - [temp.deduct]

7 प्रतिस्थापन सभी प्रकारों और अभिव्यक्तियों में होता है जो फ़ंक्शन प्रकार और टेम्पलेट पैरामीटर घोषणाओं में उपयोग किए जाते हैं। अभिव्यक्तियों में न केवल निरंतर अभिव्यक्तियां होती हैं जो कि सरणी सीमाओं में दिखाई देती हैं या नॉनटाइप टेम्पलेट तर्कों के रूप में दिखाई देती हैं, लेकिन सामान्य अभिव्यक्ति (यानी गैर-निरंतर अभिव्यक्ति) sizeof , decltype , और अन्य संदर्भों के अंदर जो निरंतर अभिव्यक्तियों को अनुमति देती हैं। प्रतिस्थापन शब्दावली क्रम में आता है और जब ऐसी स्थिति में कटौती का कारण बनता है तो विफल रहता है

सी ++ 14 में टेम्पलेट पैरामीटर से निपटने के दौरान अतिरिक्त वाक्य स्पष्ट रूप से प्रतिस्थापन के आदेश को बताता है।

प्रतिस्थापन का आदेश ऐसा कुछ है जिसे अक्सर ध्यान नहीं दिया जाता है। मुझे अभी तक एक पेपर नहीं मिला है कि यह क्यों मायने रखता है। शायद ऐसा इसलिए है क्योंकि सी ++ 1y अभी तक पूरी तरह से मानकीकृत नहीं हुआ है, लेकिन मुझे लगता है कि इस तरह के बदलाव को किसी कारण से पेश किया जाना चाहिए।

प्रश्न:

  • क्यों, और कब, टेम्पलेट तर्क प्रतिस्थापन का आदेश मायने रखता है?

जैसा कि सी ++ 14 स्पष्ट रूप से कहता है कि टेम्पलेट तर्क प्रतिस्थापन का क्रम अच्छी तरह परिभाषित है; अधिक विशेष रूप से इसे "शब्दावली क्रम में आगे बढ़ने की गारंटी दी जाएगी और जब भी एक प्रतिस्थापन कटौती विफल हो जाता है।

सी ++ 11 की तुलना में यह SFINAE -code लिखना बहुत आसान होगा जिसमें सी ++ 14 में किसी अन्य के आधार पर एक नियम होता है, हम उन मामलों से भी दूर चले जाएंगे जहां टेम्पलेट प्रतिस्थापन के अपरिभाषित आदेश से हमारा पूरा एप्लिकेशन पीड़ित हो सकता है अपरिभाषित-व्यवहार।

नोट : यह ध्यान रखना महत्वपूर्ण है कि सी ++ 14 में वर्णित व्यवहार हमेशा सी ++ 11 में भी इच्छित व्यवहार रहा है, बस इतना स्पष्ट तरीके से यह नहीं कहा गया है।

इस तरह के बदलाव के पीछे तर्क क्या है?

इस परिवर्तन के पीछे मूल कारण मूल रूप से डैनियल क्रुगलर द्वारा प्रस्तुत एक दोष रिपोर्ट में पाया जा सकता है:

आगे की व्याख्या

SFINAE लिखते समय हम डेवलपर्स के रूप में किसी भी प्रतिस्थापन को खोजने के लिए कंपाइलर पर निर्भर करते हैं जो उपयोग किए जाने पर हमारे टेम्पलेट में अमान्य प्रकार या अभिव्यक्ति उत्पन्न करेगा। यदि ऐसी अमान्य इकाई पाई जाती है तो हम जो कुछ भी टेम्पलेट घोषित कर रहे हैं उसे अवहेलना करना चाहते हैं और आशापूर्वक एक उपयुक्त मैच खोजने के लिए आगे बढ़ना चाहते हैं।

प्रतिस्थापन विफलता कोई त्रुटि नहीं है , लेकिन केवल .. "अरे, यह काम नहीं किया .. कृपया आगे बढ़ें"

समस्या यह है कि संभावित अमान्य प्रकार और अभिव्यक्ति केवल प्रतिस्थापन के तत्काल संदर्भ में देखी जाती हैं।

14.8.2 - टेम्पलेट तर्क कटौती - [temp.deduct]

8 यदि एक प्रतिस्थापन का परिणाम किसी अमान्य प्रकार या अभिव्यक्ति में होता है, तो कटौती टाइप विफल हो जाती है। एक अमान्य प्रकार या अभिव्यक्ति वह है जो प्रतिस्थापित तर्कों का उपयोग करके लिखे जाने पर बीमार हो जाएगी।

[ नोट: प्रतिस्थापन प्रक्रिया के हिस्से के रूप में प्रवेश जांच की जाती है। - नोट नोट ]

फ़ंक्शन प्रकार के तत्काल संदर्भ में केवल अमान्य प्रकार और अभिव्यक्तियां और इसके टेम्पलेट पैरामीटर प्रकारों में कटौती विफलता हो सकती है।

[ नोट: प्रतिस्थापित प्रकारों और अभिव्यक्तियों के मूल्यांकन के परिणामस्वरूप साइड इफेक्ट्स जैसे क्लास टेम्पलेट स्पेशलाइजेशन और / या फंक्शन टेम्पलेट स्पेशलाइजेशन, अंतर्निहित परिभाषित कार्यों की पीढ़ी आदि का दुष्प्रभाव हो सकता है। इस तरह के दुष्प्रभाव "तत्काल" संदर्भ "और परिणामस्वरूप कार्यक्रम खराब हो सकता है। - नोट नोट ]

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

अधिक विशेष रूप से यह टेम्पलेट रखने के बीच अंतर हो सकता है जो SFINAE में प्रयोग योग्य है, और एक टेम्पलेट जो नहीं है

सली उदाहरण

template<typename SomeType>
struct inner_type { typedef typename SomeType::type type; };

template<
  class T,
  class   = typename T::type,            // (E)
  class U = typename inner_type<T>::type // (F)
> void foo (int);                        // preferred

template<class> void foo (...);          // fallback

struct A {                 };  
struct B { using type = A; };

int main () {
  foo<A> (0); // (G), should call "fallback "
  foo<B> (0); // (H), should call "preferred"
}

चिह्नित लाइन पर (G) हम चाहते हैं कि कंपाइलर पहली बार जांच करे (E) और यदि यह मूल्यांकन (F) मूल्यांकन करता है, लेकिन इस पोस्ट में चर्चा किए गए मानक परिवर्तन से पहले ऐसी कोई गारंटी नहीं थी।


foo(int) में प्रतिस्थापन के तत्काल संदर्भ में शामिल हैं;

  • (E) यह सुनिश्चित करना कि पास में T है ::type
  • (F) यह सुनिश्चित करना कि inner_type<T> ::type inner_type<T> है ::type


यदि (F) का आकलन किया जाता है, भले ही (E) परिणाम अमान्य प्रतिस्थापन में हो, या यदि (F) का मूल्यांकन (E) से पहले किया जाता है (E) हमारा छोटा (मूर्ख) उदाहरण एसएफआईएनएई का उपयोग नहीं करेगा और हमें निदान मिलेगा कि हमारा आवेदन खराब गठित है .. भले ही हम इस तरह के मामले में इस्तेमाल करने के लिए foo(...) का इरादा रखते हैं।


नोट: ध्यान दें कि SomeType::type टेम्पलेट के तत्काल संदर्भ में नहीं है; inner_type अंदर inner_type में विफलता एप्लिकेशन को खराब inner_type और टेम्पलेट को SFINAE का उपयोग करने से रोक देगा।

सी ++ 14 में कोड विकास पर इसका क्या असर होगा?

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

यह गैर-भाषा-वकीलों के लिए एक और प्राकृतिक तरीके से टेम्पलेट तर्क प्रतिस्थापन व्यवहार करेगा; प्रतिस्थापन बाएं से दाएं होने के कारण होता है, यह एहम-जैसा-किसी भी तरह से-द-कंपाइलर-वाना-डू-इट-ए-एरम -... से कहीं अधिक सहज है।

क्या कोई नकारात्मक निहितार्थ नहीं है?

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

मैंने अभी तक इस तरह के कार्यान्वयन में ठोकर खाई है, और मुझे संदेह है कि इसका परिणाम किसी भी प्रमुख प्रदर्शन लाभ में होगा, लेकिन कम से कम विचार (सिद्धांत में) थोड़े चीजों के "नकारात्मक" पक्ष पर फिट बैठता है।

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

कहानी

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

कल्पना कीजिए कि हम गणक के साथ काम कर रहे हैं और हम निर्दिष्ट गणना के अंतर्निहित मूल्य को आसानी से प्राप्त करने का एक तरीका चाहते हैं।

असल में हम हमेशा बीमार और थके हुए होते हैं (A) , जब हम आदर्श रूप से कुछ (B) करीब चाहते हैं।

auto value = static_cast<std::underlying_type<EnumType>::type> (SOME_ENUM_VALUE); // (A)

auto value = underlying_value (SOME_ENUM_VALUE);                                  // (B)

मूल कार्यान्वयन

कहा और किया, हम underlying_value कार्यान्वयन को नीचे के रूप में देखने का निर्णय लेते हैं।

template<class T, class U = typename std::underlying_type<T>::type> 
U underlying_value (T enum_value) { return static_cast<U> (enum_value); }

यह हमारे दर्द को कम करेगा, और ऐसा लगता है कि हम वही करना चाहते हैं; हम एक गणक में गुजरते हैं, और अंतर्निहित मूल्य वापस प्राप्त करते हैं।

हम खुद को बताते हैं कि यह कार्यान्वयन भयानक है और हमारे सहयोगी ( डॉन क्विज़ोटे ) से बैठने के लिए और उत्पादन में इसे धक्का देने से पहले हमारे कार्यान्वयन की समीक्षा करने के लिए कहें।

कोड समीक्षा

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

वह हमारे कोड की समीक्षा करता है और निष्कर्ष पर आता है कि कार्यान्वयन असुरक्षित है, हमें अनिर्धारित व्यवहार से std::underlying_type रक्षा करने की आवश्यकता है क्योंकि हम एक T में पारित कर सकते हैं जो गणना प्रकार नहीं है

20.10.7.6 - अन्य परिवर्तन - [meta.trans.other]

template<class T> struct underlying_type;

हालत: T एक गणना प्रकार होगा (7.2)
टिप्पणियाँ: सदस्य टाइपपीफ type का अंतर्निहित प्रकार T का नाम होगा।

नोट: मानक underlying_type लिए एक शर्त निर्दिष्ट करता है, लेकिन यह निर्दिष्ट करने के लिए आगे नहीं जाता है कि क्या होगा यदि यह गैर-enum के साथ तत्काल हो। चूंकि हम नहीं जानते कि इस तरह के मामले में क्या होगा, उपयोग अपरिभाषित व्यवहार के अंतर्गत आता है ; यह शुद्ध यूबी हो सकता है, एप्लिकेशन को खराब बना सकता है, या ऑनलाइन अंडरवियर ऑर्डर कर सकता है।

शस्त्र शिनिंग में नाइट

डॉन इस बारे में कुछ चिल्लाता है कि हमें हमेशा सी ++ मानक का सम्मान कैसे करना चाहिए, और हमें जो कुछ भी किया है उसके लिए हमें बहुत शर्मनाक महसूस करना चाहिए .. यह अस्वीकार्य है।

वह शांत हो जाने के बाद, और कॉफी के कुछ और सिप्स थे, वह सुझाव देते हैं कि हम कार्यान्वयन को किसी भी चीज के साथ std::underlying_type को तत्काल करने के लिए सुरक्षा जोड़ने के लिए बदलते हैं।

template<
  typename T,
  typename   = typename std::enable_if<std::is_enum<T>::value>::type,  // (C)
  typename U = typename std::underlying_type<T>::type                  // (D)
>
U underlying_value (T value) { return static_cast<U> (value); }

विन्डमिल

हम अपनी खोजों के लिए डॉन का धन्यवाद करते हैं और अब हमारे कार्यान्वयन से संतुष्ट हैं, लेकिन केवल तब तक जब तक हमें एहसास न हो कि टेम्पलेट तर्क प्रतिस्थापन का क्रम सी ++ 11 में अच्छी तरह परिभाषित नहीं है (न ही यह कहा गया है कि प्रतिस्थापन कब रुक जाएगा)।

हमारे कार्यान्वयन सी ++ 11 के रूप में संकलित अभी भी एक T साथ std::underlying_type का एक क्षणिक कारण बन सकता है जो कि दो कारणों से गणना प्रकार नहीं है:

  1. संकलक (D) से पहले (D) का मूल्यांकन करने के लिए स्वतंत्र है क्योंकि प्रतिस्थापन आदेश अच्छी तरह परिभाषित नहीं है, और;

  2. भले ही संकलक (D) (C) से पहले (D) मूल्यांकन करता है, यह गारंटी नहीं देता है कि यह मूल्यांकन नहीं करेगा (D) , सी ++ 11 में कोई खंड स्पष्ट रूप से कहता नहीं है जब प्रतिस्थापन श्रृंखला बंद होनी चाहिए।

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

डॉन इस पर पवन मिट्टी से लड़ नहीं सकता है, लेकिन वह निश्चित रूप से सी ++ 11 मानक में एक बहुत ही महत्वपूर्ण ड्रैगन से चूक गया।

सी ++ 11 में एक वैध कार्यान्वयन को यह सुनिश्चित करने की आवश्यकता होगी कि कोई फर्क नहीं पड़ता कि जिस क्रम में टेम्पलेट पैरामीटर का प्रतिस्थापन std::underlying_type का std::underlying_type होता है, वह अमान्य प्रकार के साथ नहीं होगा।

#include <type_traits>

namespace impl {
  template<bool B, typename T>
  struct underlying_type { };

  template<typename T>
  struct underlying_type<true, T>
    : std::underlying_type<T>
  { };
}

template<typename T>
struct underlying_type_if_enum
  : impl::underlying_type<std::is_enum<T>::value, T>
{ };

template<typename T, typename U = typename underlying_type_if_enum<T>::type>
U get_underlying_value (T value) {
  return static_cast<U> (value);  
}

नोट: underlying_type का उपयोग किया गया था क्योंकि मानक में क्या है इसके खिलाफ मानक में कुछ उपयोग करने का यह एक आसान तरीका है; महत्वपूर्ण बात यह है कि इसे गैर-enum के साथ तत्काल करना अनिर्धारित व्यवहार है

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





c++14