C ++ SFINAE उदाहरण?


122

मैं अधिक टेम्प्लेट मेटा-प्रोग्रामिंग में आना चाहता हूं। मुझे पता है कि SFINAE का अर्थ है "प्रतिस्थापन विफलता एक त्रुटि नहीं है।" लेकिन क्या कोई मुझे SFINAE के लिए एक अच्छा उपयोग दिखा सकता है?


2
यह अच्छा प्रश्न है। मैं SFINAE को बहुत अच्छी तरह से समझता हूं, लेकिन मुझे नहीं लगता कि मुझे कभी इसका उपयोग करना पड़ा है (जब तक कि पुस्तकालय इसे जाने बिना मेरे लिए कर रहे हैं)।
जिफरे

5
एसटीएल ने इसे एफएक्यू में थोड़ा अलग तरीके से रखा , "सबस्टीट्यूशन फेल्योर इज़ नॉट ए एलीफेंट"
वल्केन रैवेन

जवाबों:


72

एक उदाहरण ( यहाँ से ):

template<typename T>
class IsClassT {
  private:
    typedef char One;
    typedef struct { char a[2]; } Two;
    template<typename C> static One test(int C::*);
    // Will be chosen if T is anything except a class.
    template<typename C> static Two test(...);
  public:
    enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
    enum { No = !Yes };
};

जब IsClassT<int>::Yesमूल्यांकन किया जाता है, तो 0 को परिवर्तित नहीं किया जा सकता है int int::*क्योंकि int एक वर्ग नहीं है, इसलिए इसमें एक सदस्य सूचक नहीं हो सकता है। यदि SFINAE मौजूद नहीं था, तो आपको एक कंपाइलर त्रुटि प्राप्त होगी, जैसे '0 को गैर-श्रेणी प्रकार int के लिए सदस्य सूचक में परिवर्तित नहीं किया जा सकता है'। इसके बजाय, यह केवल उस ...फॉर्म का उपयोग करता है जो दो लौटाता है, और इस प्रकार गलत का मूल्यांकन करता है, int एक वर्ग प्रकार नहीं है।


8
@rlbond, मैंने आपके सवाल का जवाब इस सवाल के जवाब में यहां दिया: stackoverflow.com/questions/822059/… । संक्षेप में: यदि दोनों परीक्षण कार्य उम्मीदवार और व्यवहार्य हैं, तो "..." में सबसे खराब रूपांतरण लागत है, और इसलिए इसे अन्य फ़ंक्शन के पक्ष में कभी नहीं लिया जाएगा। "..." दीर्घवृत्त, var-arg चीज है: int printf (char const *, ...);
जोहान्स शबाब -

लिंक को ब्लॉग में
index.php

20
यहाँ अजीब बात यह है कि IMO नहीं है ..., बल्कि वह है int C::*, जो मैंने कभी नहीं देखा था और इसे देखने के लिए जाना था। यहाँ क्या है और इसका क्या उपयोग हो सकता है, इसके लिए उत्तर मिला: stackoverflow.com/questions/670734/…
HostileFork का कहना है कि SE

1
क्या कोई समझा सकता है कि C: * क्या है? मैंने सभी टिप्पणियों और लिंक को पढ़ा, लेकिन मैं अभी भी सोच रहा हूं, int C :: * का अर्थ है कि यह int प्रकार का सदस्य सूचक है। क्या होगा यदि किसी वर्ग में इंट प्रकार का कोई सदस्य नहीं है? मैं क्या खो रहा हूँ? और टेस्ट <T> (0) इसमें कैसे खेलते हैं? मुझे कुछ याद आ रहा है
2:25 पर user2584960

92

मुझे SFINAEबूलियन स्थितियों की जांच करना पसंद है ।

template<int I> void div(char(*)[I % 2 == 0] = 0) {
    /* this is taken when I is even */
}

template<int I> void div(char(*)[I % 2 == 1] = 0) {
    /* this is taken when I is odd */
}

यह काफी उपयोगी हो सकता है। उदाहरण के लिए, मैंने इसका उपयोग यह जांचने के लिए किया है कि क्या ऑपरेटर कॉमा का उपयोग करके एकत्र की गई एक प्रारंभिक सूची अब एक निश्चित आकार से अधिक नहीं है

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ }
}

सूची को केवल तभी स्वीकार किया जाता है जब M, N से छोटा होता है, जिसका अर्थ है कि प्रारंभिक सूची में बहुत सारे तत्व नहीं हैं।

वाक्यविन्यास का char(*)[C]अर्थ है: तत्व प्रकार चार और आकार के साथ एक सरणी को इंगित करना C। यदि Cगलत (0 यहाँ) है, तो हमें अमान्य प्रकार मिलता है char(*)[0], सूचक को शून्य आकार के सरणी में: SFINAE इसे बनाता है ताकि टेम्पलेट को अनदेखा कर दिया जाए।

के साथ व्यक्त किया boost::enable_if, जो इस तरह दिखता है

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, 
           typename enable_if_c<(M <= N)>::type* = 0) { /* ... */ }
}

व्यवहार में, मैं अक्सर स्थितियों को एक उपयोगी क्षमता की जांच करने की क्षमता पाता हूं।


1
@ जोहान्स अजीब तरह से, जीसीसी (4.8) और क्लैंग (3.2) आकार 0 की सरणियों को घोषित करने के लिए स्वीकार करते हैं (इसलिए प्रकार वास्तव में "अमान्य" नहीं है), फिर भी यह आपके कोड पर ठीक से व्यवहार करता है। SFINAE बनाम "नियमित" प्रकारों के उपयोग के मामले में इस मामले के लिए विशेष समर्थन है।
akim

@akim: अगर यह कभी सच है (अजीब? (कब से?) तो शायद M <= N ? 1 : -1इसके बजाय काम कर सकता है।
v.oddou

1
@ v.oddou बस कोशिश करो int foo[0]। मुझे आश्चर्य नहीं है कि यह समर्थित है, क्योंकि यह बहुत उपयोगी "0-लंबाई सरणी के साथ संरचना को समाप्त करता है" ट्रिक ( gcc.gnu.org/oniltocs/gcc/Zero-Length.html )।
अकीम

@akim: हाँ, मैंने जो सोचा था -> C99। C ++ में इसकी अनुमति नहीं है, यहां आपको एक आधुनिक संकलक के साथ मिलता है:error C2466: cannot allocate an array of constant size 0
v.oddou

1
@ v.oddou नहीं, मैं वास्तव में C ++ का मतलब था, और वास्तव में C ++ 11: दोनों clang ++ और g ++ इसे स्वीकार करते हैं, और मैंने एक पृष्ठ की ओर इशारा किया है जो बताता है कि यह क्यों उपयोगी है।
एकिम

16

C ++ 11 में SFINAE परीक्षण बहुत प्रचलित हो गए हैं। यहाँ आम उपयोग के कुछ उदाहरण दिए गए हैं:

लक्षण के आधार पर एक फ़ंक्शन अधिभार चुनें

template<typename T>
std::enable_if_t<std::is_integral<T>::value> f(T t){
    //integral version
}
template<typename T>
std::enable_if_t<std::is_floating_point<T>::value> f(T t){
    //floating point version
}

एक तथाकथित प्रकार के मुहावरे का उपयोग करके आप एक प्रकार की जाँच पर बहुत मनमानी परीक्षण कर सकते हैं जैसे कि यह एक सदस्य है और यदि वह सदस्य एक निश्चित प्रकार का है

//this goes in some header so you can use it everywhere
template<typename T>
struct TypeSink{
    using Type = void;
};
template<typename T>
using TypeSinkT = typename TypeSink<T>::Type;

//use case
template<typename T, typename=void>
struct HasBarOfTypeInt : std::false_type{};
template<typename T>
struct HasBarOfTypeInt<T, TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>> :
    std::is_same<typename std::decay<decltype(std::declval<T&>().*(&T::bar))>::type,int>{};


struct S{
   int bar;
};
struct K{

};

template<typename T, typename = TypeSinkT<decltype(&T::bar)>>
void print(T){
    std::cout << "has bar" << std::endl;
}
void print(...){
    std::cout << "no bar" << std::endl;
}

int main(){
    print(S{});
    print(K{});
    std::cout << "bar is int: " << HasBarOfTypeInt<S>::value << std::endl;
}

इसका एक जीवंत उदाहरण है: http://ideone.com/dHhyHE मैंने हाल ही में अपने ब्लॉग में SFINAE और टैग प्रेषण पर एक पूरा खंड लिखा है (बेशर्म प्लग लेकिन प्रासंगिक) http://metaporky.blogspot.de/2014/08/ भाग-7 स्थैतिक-प्रेषण-function.html

C ++ 14 के रूप में ध्यान दें: एक std :: void_t है जो अनिवार्य रूप से मेरे टाइपसिंक के समान है।


कोड का आपका पहला ब्लॉक समान टेम्पलेट को फिर से परिभाषित करता है।
टीसी

चूंकि कोई प्रकार नहीं है जिसके लिए is_integral और is_floating_point दोनों सही हैं, यह या तो होना चाहिए या क्योंकि SFINAE कम से कम एक हटा देगा।
ओडिन्थेनरड

आप एक ही टेम्पलेट को अलग-अलग डिफ़ॉल्ट टेम्पलेट तर्क के साथ पुनर्परिभाषित कर रहे हैं। क्या आपने इसे संकलित करने की कोशिश की है?
टीसी

2
मैं मेटाप्रोग्रामिंग के खाके के लिए नया हूं इसलिए मैं इस उदाहरण को समझना चाहता हूं। क्या एक कारण है जो आप TypeSinkT<decltype(std::declval<T&>().*(&T::bar))>एक स्थान पर और फिर TypeSinkT<decltype(&T::bar)>दूसरे स्थान पर उपयोग करते हैं ? में भी &आवश्यक है std::declval<T&>?
केविन डॉयोन

1
आपके बारे में TypeSink, C ++ 17 में std::void_t:)
YSC

10

बूस्ट के enable_if पुस्तकालय SFINAE प्रयोग करने के लिए एक अच्छा साफ इंटरफ़ेस प्रदान करता है। मेरे पसंदीदा उपयोग उदाहरणों में से एक Boost.Iterator पुस्तकालय में है। SFINAE का उपयोग इट्रेटर प्रकार रूपांतरणों को सक्षम करने के लिए किया जाता है।


4

C ++ 17 संभवतः सुविधाओं के लिए क्वेरी करने के लिए एक सामान्य साधन प्रदान करेगा। देखें N4502 जानकारी के लिए, लेकिन एक आत्म निहित उदाहरण के रूप में निम्नलिखित पर विचार करें।

यह हिस्सा निरंतर भाग है, इसे हेडर में रखें।

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

N4502 से लिया गया निम्न उदाहरण, उपयोग दिखाता है:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

अन्य कार्यान्वयन की तुलना में, यह काफी सरल है: उपकरण का कम सेट ( void_tऔर detect) पर्याप्त है। इसके अलावा, यह बताया गया था ( N4502 देखें ) कि यह पिछले दृष्टिकोणों के अनुसार औसत रूप से अधिक कुशल (संकलन-समय और संकलक मेमोरी की खपत) है।

यहां एक जीवंत उदाहरण है , जिसमें जीसीसी प्री 5.1 के लिए पोर्टेबिलिटी ट्विक्स शामिल हैं।


3

ग्रेग रोजर्स के जवाब के आधार पर यहां एक और (देर से) SFINAE उदाहरण है :

template<typename T>
class IsClassT {
    template<typename C> static bool test(int C::*) {return true;}
    template<typename C> static bool test(...) {return false;}
public:
    static bool value;
};

template<typename T>
bool IsClassT<T>::value=IsClassT<T>::test<T>(0);

इस तरह, आप यह देखने के लिए valueमान देख सकते हैं कि Tकोई वर्ग है या नहीं:

int main(void) {
    std::cout << IsClassT<std::string>::value << std::endl; // true
    std::cout << IsClassT<int>::value << std::endl;         // false
    return 0;
}

int C::*आपके जवाब में इस वाक्य रचना का क्या अर्थ है? C::*पैरामीटर नाम कैसे हो सकता है?
किरील कोबेलेव

1
यह सदस्य के लिए एक सूचक है। कुछ संदर्भ: isocpp.org/wiki/faq/pointers-to-members
जिन

@KirillKobelev int C::*एक intसदस्य के चर के लिए एक सूचक का प्रकार है C
YSC

3

यहाँ SFINAE का एक अच्छा लेख है: C ++ की SFINAE अवधारणा से परिचय: एक वर्ग के सदस्य का संकलन-समय आत्मनिरीक्षण

इसे निम्नानुसार सारांश दें:

/*
 The compiler will try this overload since it's less generic than the variadic.
 T will be replace by int which gives us void f(const int& t, int::iterator* b = nullptr);
 int doesn't have an iterator sub-type, but the compiler doesn't throw a bunch of errors.
 It simply tries the next overload. 
*/
template <typename T> void f(const T& t, typename T::iterator* it = nullptr) { }

// The sink-hole.
void f(...) { }

f(1); // Calls void f(...) { }

template<bool B, class T = void> // Default template version.
struct enable_if {}; // This struct doesn't define "type" and the substitution will fail if you try to access it.

template<class T> // A specialisation used if the expression is true. 
struct enable_if<true, T> { typedef T type; }; // This struct do have a "type" and won't fail on access.

template <class T> typename enable_if<hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return obj.serialize();
}

template <class T> typename enable_if<!hasSerialize<T>::value, std::string>::type serialize(const T& obj)
{
    return to_string(obj);
}

declvalएक उपयोगिता है जो आपको एक प्रकार के ऑब्जेक्ट के लिए "नकली संदर्भ" देती है जिसे आसानी से नहीं बनाया जा सकता है। declvalहमारे SFINAE निर्माण के लिए वास्तव में उपयोगी है।

struct Default {
    int foo() const {return 1;}
};

struct NonDefault {
    NonDefault(const NonDefault&) {}
    int foo() const {return 1;}
};

int main()
{
    decltype(Default().foo()) n1 = 1; // int n1
//  decltype(NonDefault().foo()) n2 = n1; // error: no default constructor
    decltype(std::declval<NonDefault>().foo()) n2 = n1; // int n2
    std::cout << "n2 = " << n2 << '\n';
}

0

यहां, मैं यह निर्धारित करने के लिए टेम्प्लेट फ़ंक्शन ओवरलोडिंग का उपयोग कर रहा हूं (सीधे SFINAE नहीं) कि क्या कोई सूचक एक फ़ंक्शन या सदस्य वर्ग सूचक है: ( iostream cout / cerr सदस्य फ़ंक्शन पॉइंटर्स को 1 या सत्य के रूप में मुद्रित किया जा रहा है? )

https://godbolt.org/z/c2NmzR

#include<iostream>

template<typename Return, typename... Args>
constexpr bool is_function_pointer(Return(*pointer)(Args...)) {
    return true;
}

template<typename Return, typename ClassType, typename... Args>
constexpr bool is_function_pointer(Return(ClassType::*pointer)(Args...)) {
    return true;
}

template<typename... Args>
constexpr bool is_function_pointer(Args...) {
    return false;
}

struct test_debugger { void var() {} };
void fun_void_void(){};
void fun_void_double(double d){};
double fun_double_double(double d){return d;}

int main(void) {
    int* var;

    std::cout << std::boolalpha;
    std::cout << "0. " << is_function_pointer(var) << std::endl;
    std::cout << "1. " << is_function_pointer(fun_void_void) << std::endl;
    std::cout << "2. " << is_function_pointer(fun_void_double) << std::endl;
    std::cout << "3. " << is_function_pointer(fun_double_double) << std::endl;
    std::cout << "4. " << is_function_pointer(&test_debugger::var) << std::endl;
    return 0;
}

प्रिंटों

0. false
1. true
2. true
3. true
4. true

कोड है, यह हो सकता है (संकलक "अच्छा" के आधार पर होगा) एक समारोह जो सही या गलत वापस आ जाएगी करने के लिए एक रन समय कॉल उत्पन्न करते हैं। यदि आप is_function_pointer(var)संकलित प्रकार पर मूल्यांकन करने के लिए बाध्य करना चाहते हैं (कोई फ़ंक्शन कॉल रन समय में नहीं किया गया है), तो आप constexprचर चाल का उपयोग कर सकते हैं :

constexpr bool ispointer = is_function_pointer(var);
std::cout << "ispointer " << ispointer << std::endl;

C ++ मानक द्वारा, सभी constexprचर का संकलन समय पर मूल्यांकन करने की गारंटी दी जाती है (संकलन समय में C स्ट्रिंग की लंबाई की गणना करना। क्या यह वास्तव में एक बाधा है? )।


0

निम्नलिखित कोड SFINAE का उपयोग करता है कि कंपाइलर एक प्रकार का निश्चित तरीका है या नहीं इसके आधार पर एक अधिभार का चयन करें:

    #include <iostream>
    
    template<typename T>
    void do_something(const T& value, decltype(value.get_int()) = 0) {
        std::cout << "Int: " <<  value.get_int() << std::endl;
    }
    
    template<typename T>
    void do_something(const T& value, decltype(value.get_float()) = 0) {
        std::cout << "Float: " << value.get_float() << std::endl;
    }
    
    
    struct FloatItem {
        float get_float() const {
            return 1.0f;
        }
    };
    
    struct IntItem {
        int get_int() const {
            return -1;
        }
    };
    
    struct UniversalItem : public IntItem, public FloatItem {};
    
    int main() {
        do_something(FloatItem{});
        do_something(IntItem{});
        // the following fails because template substitution
        // leads to ambiguity 
        // do_something(UniversalItem{});
        return 0;
    }

आउटपुट:

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