std :: enable_if सशर्त रूप से एक सदस्य फ़ंक्शन को संकलित करने के लिए


156

मैं कैसे उपयोग करने के लिए समझने के लिए काम करने के लिए एक सरल उदाहरण प्राप्त करने की कोशिश कर रहा हूं std::enable_ifइस उत्तर को पढ़ने के बाद , मैंने सोचा कि एक साधारण उदाहरण के साथ आना बहुत कठिन नहीं होना चाहिए। मैं std::enable_ifदो सदस्य-कार्यों के बीच चयन करना चाहता हूं और उनमें से केवल एक का उपयोग करने की अनुमति देता हूं।

दुर्भाग्य से, निम्नलिखित जीसीसी 4.7 के साथ संकलन नहीं करता है और घंटों और घंटों की कोशिश के बाद मैं आप लोगों से पूछ रहा हूं कि मेरी गलती क्या है।

#include <utility>
#include <iostream>

template< class T >
class Y {

    public:
        template < typename = typename std::enable_if< true >::type >
        T foo() {
            return 10;
        }
        template < typename = typename std::enable_if< false >::type >
        T foo() {
            return 10;
        }

};


int main() {
    Y< double > y;

    std::cout << y.foo() << std::endl;
}

gcc निम्नलिखित समस्याओं की रिपोर्ट करता है:

% LANG=C make CXXFLAGS="-std=c++0x" enable_if
g++ -std=c++0x    enable_if.cpp   -o enable_if
enable_if.cpp:12:65: error: `type' in `struct std::enable_if<false>' does not name a type
enable_if.cpp:13:15: error: `template<class T> template<class> T Y::foo()' cannot be overloaded
enable_if.cpp:9:15: error: with `template<class T> template<class> T Y::foo()'

G ++ दूसरे सदस्य फ़ंक्शन के लिए गलत इंस्टेंशन को क्यों नहीं हटाता है? मानक के अनुसार, std::enable_if< bool, T = void >::typeकेवल तब मौजूद होता है जब बूलियन टेम्पलेट पैरामीटर सही होता है। लेकिन g ++ इसे SFINAE क्यों नहीं मानता? मुझे लगता है कि ओवरलोडिंग त्रुटि संदेश इस समस्या से आता है कि g ++ दूसरे सदस्य फ़ंक्शन को नहीं हटाता है और मानता है कि यह एक अधिभार होना चाहिए।


1
मुझे यकीन नहीं है, लेकिन मुझे लगता है कि यह निम्नलिखित है: enable_if SFINAE पर आधारित है (प्रतिस्थापन विफलता एक त्रुटि नहीं है)। हालांकि, आपके पास यहां कोई प्रतिस्थापन नहीं है, क्योंकि कोई भी पैरामीटर यह निर्धारित करने के लिए उपयोग नहीं किया जा सकता है कि किस ओवरलोड का उपयोग करना है। आपको "सही" und "असत्य" को T पर निर्भर करना चाहिए (मुझे पता है कि आप इसे सरल उदाहरण में नहीं करना चाहते थे, लेकिन यह शायद अब बहुत सरल है ...)
फिलिप

3
मैंने इसके बारे में भी सोचा और उपयोग करने का प्रयास किया std::is_same< T, int >::valueऔर ! std::is_same< T, int >::valueजो समान परिणाम देता है।
अवनु

जवाबों:


117

SFINAE केवल तभी काम करता है जब टेम्पलेट टेम्पलेट के तर्क कटौती में प्रतिस्थापन निर्माण को बीमार बनाता है। ऐसा कोई विकल्प नहीं है।

मैंने इसके बारे में भी सोचा और उपयोग करने का प्रयास किया std::is_same< T, int >::valueऔर ! std::is_same< T, int >::valueजो समान परिणाम देता है।

ऐसा इसलिए है क्योंकि जब क्लास टेम्प्लेट को त्वरित किया जाता है (जो तब होता है जब आप Y<int>अन्य मामलों के बीच एक प्रकार की वस्तु बनाते हैं), तो यह उसके सभी सदस्य घोषणाओं को जरूरी कर देता है (जरूरी नहीं कि उनकी परिभाषा / निकाय!)। इनमें इसके सदस्य टेम्पलेट भी हैं। ध्यान दें कि Tतब जाना जाता है, और !std::is_same< T, int >::valueगलत उपज देता है। तो यह एक वर्ग बनाएगा Y<int>जिसमें शामिल है

class Y<int> {
    public:
        /* instantiated from
        template < typename = typename std::enable_if< 
          std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< true >::type >
        int foo();

        /* instantiated from

        template < typename = typename std::enable_if< 
          ! std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< false >::type >
        int foo();
};

std::enable_if<false>::type, एक गैर-मौजूद प्रकार तक पहुँचता है तो यह है कि घोषणा बीमार ही बना है। और इस प्रकार आपका प्रोग्राम अमान्य है।

आपको सदस्य टेम्प्लेट बनाने के लिए सदस्य टेम्पलेट के enable_ifएक पैरामीटर पर ही निर्भर रहना होगा । फिर घोषणाएं मान्य हैं, क्योंकि पूरा प्रकार अभी भी निर्भर है। जब आप उनमें से किसी एक को कॉल करने का प्रयास करते हैं, तो उनके टेम्पलेट तर्कों के लिए तर्क में कटौती होती है और SFINAE अपेक्षा के अनुसार होता है। इस सवाल और इसी उत्तर को देखें कि यह कैसे करना है।


14
... बस स्पष्ट करने के लिए, यदि यह उपयोगी है: जब Yटेम्पलेट वर्ग का एक उदाहरण त्वरित किया जाता है, तो संकलक वास्तव में टेम्पलेट सदस्य कार्यों को संकलित नहीं करेगा; हालाँकि, संकलक Tसदस्य टेम्पलेट के प्रतिस्थापन में प्रदर्शन करेगा, ताकि बाद के समय में इन सदस्य टेम्प्लेट को तत्काल किया जा सके। विफलता का यह बिंदु SFINAE नहीं है, क्योंकि SFINAE केवल तभी लागू होता है जब अधिभार संकल्प के लिए संभावित कार्यों के सेट का निर्धारण किया जाता है , और किसी वर्ग को तुरंत लोड करने के लिए अधिभार संकल्प के लिए कार्यों के सेट का निर्धारण करने का मामला नहीं होता है। (या तो मुझे लगता है!)
दान निसेनबाम

93

मैंने यह छोटा उदाहरण दिया जो काम भी करता है।

#include <iostream>
#include <type_traits>

class foo;
class bar;

template<class T>
struct is_bar
{
    template<class Q = T>
    typename std::enable_if<std::is_same<Q, bar>::value, bool>::type check()
    {
        return true;
    }

    template<class Q = T>
    typename std::enable_if<!std::is_same<Q, bar>::value, bool>::type check()
    {
        return false;
    }
};

int main()
{
    is_bar<foo> foo_is_bar;
    is_bar<bar> bar_is_bar;
    if (!foo_is_bar.check() && bar_is_bar.check())
        std::cout << "It works!" << std::endl;

    return 0;
}

यदि आप चाहते हैं कि मैं विस्तार से टिप्पणी करूं। मुझे लगता है कि कोड अधिक या कम आत्म-व्याख्यात्मक है, लेकिन फिर मैंने इसे फिर से बनाया ताकि मैं गलत हो सकूं :)

आप इसे यहां कार्रवाई में देख सकते हैं ।


2
यह VS2012 पर संकलन नहीं करता है। error C4519: default template arguments are only allowed on a class template
पाइथननॉट

1
बदकिस्मती से। मैंने केवल इसे gcc के साथ परखा। शायद इससे मदद मिलती है: stackoverflow.com/a/17543296/660982
jpihl

1
यह निश्चित रूप से यहाँ सबसे अच्छा जवाब है और ठीक वही है जिसकी मुझे तलाश थी।
वेइपेंग एल

3
Qइसके बराबर होते हुए भी एक और टेम्पलेट क्लास बनाने की आवश्यकता क्यों है T?
ilya1725

1
क्योंकि आपको testसदस्य फ़ंक्शन को टेम्पलेट करने की आवश्यकता है । दोनों एक ही समय में मौजूद नहीं हो सकते। Qबस आगे की ओर वर्ग टेम्पलेट प्रकार T। आप क्लास के टेम्प्लेट को हटा सकते हैं Tजैसे: cpp.sh/4nxw लेकिन यह किन्दा उद्देश्य को हरा देता है।
jpihl

13

उन देर से काम करने वालों के लिए जो एक समाधान की तलाश में हैं जो "बस काम करता है":

#include <utility>
#include <iostream>

template< typename T >
class Y {

    template< bool cond, typename U >
    using resolvedType  = typename std::enable_if< cond, U >::type; 

    public:
        template< typename U = T > 
        resolvedType< true, U > foo() {
            return 11;
        }
        template< typename U = T >
        resolvedType< false, U > foo() {
            return 12;
        }

};


int main() {
    Y< double > y;

    std::cout << y.foo() << std::endl;
}

संकलन:

g++ -std=gnu++14 test.cpp 

रनिंग देता है:

./a.out 
11

6
उम, तुम क्यों नाम बदलने हैं std::enable_if_tकरने के लिए resolvedType
क्वर्टी

1
क्योंकि हर कोई व्यापक रूप से भिन्न हो सकने वाले कारणों के लिए C ++ 17 का उपयोग नहीं कर सकता है।
जेम्स यांग

9

से इस पोस्ट:

डिफ़ॉल्ट टेम्पलेट तर्क किसी टेम्पलेट के हस्ताक्षर का हिस्सा नहीं हैं

लेकिन कुछ इस तरह कर सकते हैं:

#include <iostream>

struct Foo {
    template < class T,
               class std::enable_if < !std::is_integral<T>::value, int >::type = 0 >
    void f(const T& value)
    {
        std::cout << "Not int" << std::endl;
    }

    template<class T,
             class std::enable_if<std::is_integral<T>::value, int>::type = 0>
    void f(const T& value)
    {
        std::cout << "Int" << std::endl;
    }
};

int main()
{
    Foo foo;
    foo.f(1);
    foo.f(1.1);

    // Output:
    // Int
    // Not int
}

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

5

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

template< class T, bool condition> struct FooImpl;
template<class T> struct FooImpl<T, true> {
T foo() { return 10; }
};

template<class T> struct FoolImpl<T,false> {
T foo() { return 5; }
};

template< class T >
class Y : public FooImpl<T, boost::is_integer<T> > // whatever your test is goes here.
{
public:
    typedef FooImpl<T, boost::is_integer<T> > inherited;

    // you will need to use "inherited::" if you want to name any of the 
    // members of those inherited classes.
};

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

उदाहरण के लिए:

template<class T, bool condition> class Goo;
// repeat pattern above.

template<class T, bool condition>
class Foo<T, true> : public Goo<T, boost::test<T> > {
public:
    typedef Goo<T, boost::test<T> > inherited:
    // etc. etc.
};

4

बूलियन को कटौती किए जाने वाले टेम्पलेट पैरामीटर पर निर्भर होने की आवश्यकता है। तो तय करने का एक आसान तरीका एक डिफ़ॉल्ट बूलियन पैरामीटर का उपयोग करना है:

template< class T >
class Y {

    public:
        template < bool EnableBool = true, typename = typename std::enable_if<( std::is_same<T, double>::value && EnableBool )>::type >
        T foo() {
            return 10;
        }

};

हालाँकि, यह कार्य नहीं करेगा यदि आप सदस्य फ़ंक्शन को ओवरलोड करना चाहते हैं। इसके बजाय, टिक पुस्तकालय TICK_MEMBER_REQUIRESसे उपयोग करने के लिए इसका सबसे अच्छा :

template< class T >
class Y {

    public:
        TICK_MEMBER_REQUIRES(std::is_same<T, double>::value)
        T foo() {
            return 10;
        }

        TICK_MEMBER_REQUIRES(!std::is_same<T, double>::value)
        T foo() {
            return 10;
        }

};

आप अपने स्वयं के सदस्य को भी इस तरह से मैक्रो की आवश्यकता कर सकते हैं (यदि आप किसी अन्य लाइब्रेरी का उपयोग नहीं करना चाहते हैं तो):

template<long N>
struct requires_enum
{
    enum class type
    {
        none,
        all       
    };
};


#define MEMBER_REQUIRES(...) \
typename requires_enum<__LINE__>::type PrivateRequiresEnum ## __LINE__ = requires_enum<__LINE__>::type::none, \
class=typename std::enable_if<((PrivateRequiresEnum ## __LINE__ == requires_enum<__LINE__>::type::none) && (__VA_ARGS__))>::type

यह मेरे लिए उस तरह से काम नहीं किया। मैय्यब कुछ याद कर रहा है? क्या आप कृपया कार्य रूप में ओपी उदाहरण को फिर से लिख सकते हैं?
user1284631

ओवरलोडिंग के साथ मूल उदाहरण काम नहीं करता है। मैंने अपना उत्तर अपडेट किया कि आप इसे ओवरलोडिंग के साथ कैसे कर सकते हैं।
पॉल फुल्ट्ज़ II

0

यहाँ एक मैक्रो का उपयोग करके मेरा न्यूनतम उदाहरण है। enable_if((...))अधिक जटिल अभिव्यक्तियों का उपयोग करते समय डबल ब्रैकेट का उपयोग करें ।

template<bool b, std::enable_if_t<b, int> = 0>
using helper_enable_if = int;

#define enable_if(value) typename = helper_enable_if<value>

struct Test
{
     template<enable_if(false)>
     void run();
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.