मुझे फ़ंक्शन हस्ताक्षर में std :: enable_if से क्यों बचना चाहिए


165

स्कॉट मेयर्स ने अपनी अगली पुस्तक EC ++ 11 की सामग्री और स्थिति पोस्ट की । उन्होंने लिखा कि पुस्तक में एक आइटम " std::enable_ifफ़ंक्शन हस्ताक्षरों से बचें" हो सकता है ।

std::enable_if फ़ंक्शन तर्क के रूप में, रिटर्न प्रकार के रूप में या क्लास टेम्पलेट या फ़ंक्शन टेम्पलेट पैरामीटर के रूप में अधिभार संकल्प से कार्यों या कक्षाओं को सशर्त रूप से हटाने के लिए उपयोग किया जा सकता है।

में इस सवाल सभी तीन समाधान दिखाई जाती हैं।

फ़ंक्शन पैरामीटर के रूप में:

template<typename T>
struct Check1
{
   template<typename U = T>
   U read(typename std::enable_if<
          std::is_same<U, int>::value >::type* = 0) { return 42; }

   template<typename U = T>
   U read(typename std::enable_if<
          std::is_same<U, double>::value >::type* = 0) { return 3.14; }   
};

टेम्पलेट पैरामीटर के रूप में:

template<typename T>
struct Check2
{
   template<typename U = T, typename std::enable_if<
            std::is_same<U, int>::value, int>::type = 0>
   U read() { return 42; }

   template<typename U = T, typename std::enable_if<
            std::is_same<U, double>::value, int>::type = 0>
   U read() { return 3.14; }   
};

वापसी प्रकार के रूप में:

template<typename T>
struct Check3
{
   template<typename U = T>
   typename std::enable_if<std::is_same<U, int>::value, U>::type read() {
      return 42;
   }

   template<typename U = T>
   typename std::enable_if<std::is_same<U, double>::value, U>::type read() {
      return 3.14;
   }   
};
  • कौन सा समाधान पसंद किया जाना चाहिए और मुझे दूसरों से क्यों बचना चाहिए?
  • किन मामलों में " std::enable_ifफंक्शन सिग्नेचर से बचें " चिंताओं का उपयोग रिटर्न प्रकार के रूप में किया जाता है (जो सामान्य फ़ंक्शन सिग्नेचर का हिस्सा नहीं है, लेकिन टेम्प्लेट स्पेशलाइज़ेशन का है)?
  • क्या सदस्य और गैर-सदस्य फ़ंक्शन टेम्पलेट के लिए कोई अंतर हैं?

क्योंकि ओवरलोडिंग सिर्फ उतना ही अच्छा है, आमतौर पर। यदि कुछ भी हो, तो उस कार्यान्वयन के लिए एक प्रतिनिधि का उपयोग करें जो (विशेष) वर्ग टेम्पलेट का उपयोग करता है।
sehe

सदस्य कार्यों में भिन्नता है कि अधिभार सेट में वर्तमान अधिभार के बाद घोषित ओवरलोड शामिल हैं । यह विशेष रूप से महत्वपूर्ण है जब कर variadics वापसी प्रकार (जहां वापसी प्रकार एक और अधिभार से अनुमान लगाया जा रहा है) में देरी
sehe

1
ठीक है, केवल आत्मगत मैं कहना है कि, जबकि अक्सर काफी उपयोगी किया जा रहा है मुझे पसंद नहीं है है std::enable_ifमेरी समारोह हस्ताक्षर (विशेष रूप से बदसूरत अतिरिक्त अस्त-व्यस्त करे nullptrसमारोह तर्क संस्करण), क्योंकि यह हमेशा (कुछ एक के लिए यह क्या है की तरह लग रहा है, एक अजीब हैक static ifहो सकता है इंटररेजिंग लैंग्वेज फीचर का फायदा उठाने के लिए टेम्प्लेट ब्लैक-मैजिक का उपयोग करके बहुत अधिक सुंदर और साफ) करते हैं। यही कारण है कि जब भी संभव हो मैं टैग-प्रेषण को प्राथमिकता देता हूं (ठीक है, आपके पास अभी भी अतिरिक्त अजीब तर्क हैं, लेकिन सार्वजनिक इंटरफ़ेस में नहीं और बहुत कम बदसूरत और गूढ़ हैं )।
ईसाई राऊ

2
मैं क्या करता है पूछना चाहता हूँ =0में typename std::enable_if<std::is_same<U, int>::value, int>::type = 0पूरा? मुझे इसे समझने के लिए सही संसाधन नहीं मिले। मुझे पता है कि पहले भाग =0में एक सदस्य प्रकार है intअगर Uऔर intसमान है। बहुत धन्यवाद!
astroboylrx

4
@astroboylrx फनी, मैं सिर्फ एक टिप्पणी करने जा रहा था जो इस पर ध्यान दे रहा था। मूल रूप से, यह = 0 इंगित करता है कि यह एक डिफ़ॉल्ट, गैर-प्रकार टेम्पलेट पैरामीटर है। यह इस तरह से किया गया है क्योंकि डिफ़ॉल्ट प्रकार टेम्पलेट पैरामीटर हस्ताक्षर का हिस्सा नहीं हैं, इसलिए आप उन पर अधिभार नहीं डाल सकते।
निर् फ्राइडमैन

जवाबों:


107

टेम्पलेट मापदंडों में हैक रखो

enable_ifटेम्पलेट पैरामीटर पर दृष्टिकोण दूसरों पर कम से कम दो लाभ हैं:

  • पठनीयता : enable_if का उपयोग और वापसी / तर्क प्रकारों को एक साथ टाइप नहीं किया जा सकता है, जो कि टाइपमेनेन डिसएम्बिग्यूटर्स और नेस्टेड टाइप एक्सेस के एक गन्दे चंक में एक साथ होते हैं; भले ही डिसएम्बिगेटर और नेस्टेड प्रकार की अव्यवस्था को उर्फ ​​टेम्प्लेट के साथ कम किया जा सकता है, जो अभी भी दो असंबंधित चीजों को एक साथ मिला देगा। Enable_if का उपयोग टेम्पलेट पैरामीटर से संबंधित है जो रिटर्न प्रकारों के लिए नहीं है। टेम्पलेट मापदंडों में उनके होने का मतलब है कि वे किन मामलों के करीब हैं;

  • सार्वभौमिक प्रयोज्यता : कंस्ट्रक्टर के पास रिटर्न प्रकार नहीं होते हैं, और कुछ ऑपरेटरों के पास अतिरिक्त तर्क नहीं हो सकते हैं, इसलिए हर जगह अन्य दो विकल्पों में से कोई भी लागू नहीं किया जा सकता है। एक टेम्पलेट पैरामीटर में enable_if डालना हर जगह काम करता है क्योंकि आप केवल वैसे भी टेम्पलेट्स पर SFINAE का उपयोग कर सकते हैं।

मेरे लिए, पठनीयता पहलू इस पसंद में बड़ा प्रेरक कारक है।


4
यहाँFUNCTION_REQUIRES मैक्रो का उपयोग करना , इसे पढ़ने के लिए बहुत अच्छा बनाता है, और यह C ++ 03 कंपाइलर में भी काम करता है, और यह रिटर्न प्रकार में उपयोग करने पर निर्भर करता है । साथ ही, फंक्शन टेम्प्लेट मापदंडों के उपयोग से ओवरलोडिंग की समस्या पैदा होती है, क्योंकि अब फंक्शन सिग्नेचर अद्वितीय नहीं हैं, जिससे अस्पष्ट ओवरलोडिंग त्रुटियां होती हैं। enable_ifenable_if
पॉल फुल्ट्ज़ II

3
यह एक पुराना सवाल है, लेकिन अभी भी पढ़ने वाले किसी के लिए: @Paul द्वारा उठाए गए मुद्दे का समाधान enable_ifएक डिफ़ॉल्ट गैर-प्रकार टेम्पलेट पैरामीटर के साथ उपयोग करना है, जो ओवरलोडिंग की अनुमति देता है। के enable_if_t<condition, int> = 0बजाय Ie typename = enable_if_t<condition>
Nir Friedman

: लगभग स्थैतिक-अगर के लिए लिंक वेबैक web.archive.org/web/20150726012736/http://flamingdangerzone.com/...
davidbak

@ R.MartinhoFernandes flamingdangerzoneआपकी टिप्पणी में लिंक अब एक स्पाइवेयर-स्थापित पृष्ठ पर ले जाता है। मैंने इसे मॉडरेटर के ध्यान के लिए हरी झंडी दिखाई।
nispio

58

std::enable_ifटेम्पलेट तर्क में कटौती के दौरान " पदार्थ विफलता " एक त्रुटि नहीं है (उर्फ SFINAE) सिद्धांत पर निर्भर करता है । यह एक बहुत ही नाजुक भाषा की विशेषता है और इसे सही करने के लिए आपको बहुत सावधान रहने की आवश्यकता है।

  1. यदि आपकी स्थिति enable_ifमें एक नेस्टेड टेम्पलेट या प्रकार की परिभाषा है (संकेत: ::टोकन की तलाश करें), तो इन नेस्टेड टेम्पल या प्रकार के संकल्प आमतौर पर एक गैर- कटौती किए गए संदर्भ होते हैं । इस तरह के गैर-कटौती किए गए संदर्भ पर कोई प्रतिस्थापन विफलता एक त्रुटि है
  2. कई enable_ifअधिभार में विभिन्न स्थितियों में कोई अतिव्याप्ति नहीं हो सकती क्योंकि अधिभार संकल्प अस्पष्ट होगा। यह कुछ ऐसा है जो आपको एक लेखक के रूप में खुद को जांचने की आवश्यकता है, हालांकि आपको अच्छा संकलक चेतावनी मिलेगी।
  3. enable_ifओवरलोड रिज़ॉल्यूशन के दौरान व्यवहार्य फ़ंक्शन के सेट में हेरफेर करता है जिसमें अन्य कार्यों की उपस्थिति के आधार पर आश्चर्यजनक बातचीत हो सकती है जो अन्य स्कोप (जैसे एडीएल के माध्यम से) से लाए जाते हैं। यह इसे बहुत मजबूत नहीं बनाता है।

संक्षेप में, जब यह काम करता है तो यह काम करता है, लेकिन जब यह नहीं होता है तो यह डीबग करना बहुत कठिन हो सकता है। एक बहुत अच्छा विकल्प टैग डिस्पैचिंग का उपयोग करना है , अर्थात कार्यान्वयन फ़ंक्शन (आमतौर पर एक detailनामस्थान या सहायक श्रेणी में) को सौंपने के लिए, जो उसी संकलन-समय की स्थिति के आधार पर एक डमी तर्क प्राप्त करता है जिसका आप उपयोग करते हैं enable_if

template<typename T>
T fun(T arg) 
{ 
    return detail::fun(arg, typename some_template_trait<T>::type() ); 
}

namespace detail {
    template<typename T>
    fun(T arg, std::false_type /* dummy */) { }

    template<typename T>
    fun(T arg, std::true_type /* dummy */) {}
}

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


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

@ R.MartinhoFernandes क्या आप एक छोटा उदाहरण दे सकते हैं, और enable_ifयह बता सकते हैं कि यह सही कैसे होगा?
TemplateRex

1
@ R.MartinhoFernandes मुझे लगता है कि इन बिंदुओं को समझाने वाला एक अलग उत्तर ओपी के लिए मूल्य जोड़ सकता है। :-) BTW, जैसे लक्षण लिखना is_f_ableकुछ ऐसा है जो मैं पुस्तकालय लेखकों के लिए एक कार्य पर विचार करता हूं जो निश्चित रूप से SFINAE का उपयोग कर सकते हैं जब उन्हें लाभ मिलता है, लेकिन "नियमित" उपयोगकर्ताओं के लिए और एक लक्षण दिया गया is_f_able, मुझे लगता है कि टैग प्रेषण आसान है।
TemplateRex

1
@hansmaad मैंने आपके प्रश्न को संबोधित करते हुए एक संक्षिप्त उत्तर पोस्ट किया है, और इसके बजाय एक ब्लॉग पोस्ट में "SFINAE के लिए या SFINAE नहीं" के मुद्दे को संबोधित करेंगे (यह इस सवाल पर थोड़ा सा विषय है)। जैसे ही मुझे इसे खत्म करने का समय मिलता है, मेरा मतलब है।
आर। मार्टिनो फर्नांडिस

8
SFINAE "नाजुक" है? क्या?
को ऑर्बिट में

5

कौन सा समाधान पसंद किया जाना चाहिए और मुझे दूसरों से क्यों बचना चाहिए?

  • टेम्पलेट पैरामीटर

    • यह कंस्ट्रक्टर्स में प्रयोग करने योग्य है।
    • यह उपयोगकर्ता द्वारा परिभाषित रूपांतरण ऑपरेटर में प्रयोग करने योग्य है।
    • इसके लिए C ++ 11 या बाद की आवश्यकता होती है।
    • यह IMO है, अधिक पठनीय है।
    • यह आसानी से गलत तरीके से उपयोग किया जा सकता है और ओवरलोड के साथ त्रुटियों का उत्पादन करता है:

      template<typename T, typename = std::enable_if_t<std::is_same<T, int>::value>>
      void f() {/*...*/}
      
      template<typename T, typename = std::enable_if_t<std::is_same<T, float>::value>>
      void f() {/*...*/} // Redefinition: both are just template<typename, typename> f()

    typename = std::enable_if_t<cond>सही के बजाय नोटिसstd::enable_if_t<cond, int>::type = 0

  • वापसी प्रकार:

    • इसका उपयोग कंस्ट्रक्टर में नहीं किया जा सकता है। (कोई वापसी प्रकार नहीं)
    • इसका उपयोग उपयोगकर्ता द्वारा परिभाषित रूपांतरण ऑपरेटर में नहीं किया जा सकता है। (कटौती योग्य नहीं)
    • यह प्री-सी ++ 11 का उपयोग किया जा सकता है।
    • दूसरा अधिक पठनीय IMO।
  • अंतिम, फंक्शन पैरामीटर में:

    • यह प्री-सी ++ 11 का उपयोग किया जा सकता है।
    • यह कंस्ट्रक्टर्स में प्रयोग करने योग्य है।
    • इसका उपयोग उपयोगकर्ता द्वारा परिभाषित रूपांतरण ऑपरेटर में नहीं किया जा सकता है। (कोई पैरामीटर नहीं)
    • यह तर्क की निश्चित संख्या के साथ तरीकों में इस्तेमाल नहीं किया जा सकता है (एकल / बाइनरी ऑपरेटरों +, -, *, ...)
    • यह सुरक्षित रूप से विरासत में उपयोग किया जा सकता है (नीचे देखें)।
    • फ़ंक्शन हस्ताक्षर बदलें (आपके पास मूल रूप से अंतिम तर्क के रूप में एक अतिरिक्त है void* = nullptr) (इसलिए फ़ंक्शन पॉइंटर अलग होगा, और इसी तरह)

क्या सदस्य और गैर-सदस्य फ़ंक्शन टेम्पलेट के लिए कोई अंतर हैं?

वंशानुक्रम के साथ सूक्ष्म अंतर हैं और using:

using-declarator(जोर मेरा) के अनुसार :

namespace.udecl

उपयोगकर्ता-घोषणाकर्ता द्वारा शुरू की गई घोषणाओं का सेट योग्य-नाम लुकअप ([basic.lookup.qual], [class.member.lookup]) का उपयोग करके-घोषणाकर्ता में नाम के लिए पाया जाता है, जो वर्णित कार्यों को छिपाकर किया जाता है। नीचे।

...

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

तो टेम्पलेट तर्क और वापसी प्रकार दोनों के लिए, तरीके छिपे हुए हैं परिदृश्य निम्नलिखित है:

struct Base
{
    template <std::size_t I, std::enable_if_t<I == 0>* = nullptr>
    void f() {}

    template <std::size_t I>
    std::enable_if_t<I == 0> g() {}
};

struct S : Base
{
    using Base::f; // Useless, f<0> is still hidden
    using Base::g; // Useless, g<0> is still hidden

    template <std::size_t I, std::enable_if_t<I == 1>* = nullptr>
    void f() {}

    template <std::size_t I>
    std::enable_if_t<I == 1> g() {}
};

डेमो (गलत तरीके से बेस फंक्शन पाता है)।

जबकि तर्क के साथ, समान परिदृश्य काम करता है:

struct Base
{
    template <std::size_t I>
    void h(std::enable_if_t<I == 0>* = nullptr) {}
};

struct S : Base
{
    using Base::h; // Base::h<0> is visible

    template <std::size_t I>
    void h(std::enable_if_t<I == 1>* = nullptr) {}
};

डेमो

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.