2012-02-18 11 views
12

मेरे पास एक ऐसा फ़ंक्शन है जो एक कंटेनर पर पुनरावृत्त करता है और फ़िल्टरिंग के लिए प्रत्येक तत्व को भविष्य में पास करता है। इस फ़ंक्शन का अधिभार प्रत्येक तत्व की अनुक्रमणिका को भविष्य में भी पास करता है।क्या सी ++ लैम्बडा सही ढंग से अधिभारित फ़ंक्शंस का चयन नहीं करते हैं?

template<typename TContainer> 
void DoSomethingIf(TContainer &c, std::function<bool (const typename TContainer::const_reference)> predicate); 

template<typename TContainer> 
void DoSomethingIf(TContainer &c, std::function<bool (const typename TContainer::const_reference, int)> predicate); 

मैं ने पाया है कि एक नग्न लैम्ब्डा साथ या तो इन कार्यों के फोन करने का प्रयास, VC11 में एक संकलक त्रुटि का कारण एक std :: समारोह वस्तु का उपयोग करते समय सफल होगा होगा:

void foo() 
{ 
    std::vector<int> v; 

    // fails 
    DoSomethingIf(v, [](const int &x) { return x == 0; }); 

    // also fails 
    auto lambda = [](const int &x) { return x == 0; }; 
    DoSomethingIf(v, lambda); 

    // success! 
    std::function<bool (const int &)> fn = [](const int &x) { return x == 0; }; 
    DoSomethingIf(v, fn); 
} 

1>c:\users\moswald\test.cpp(15): error C2668: 'DoSomethingIf' : ambiguous call to overloaded function 
1>   c:\users\moswald\test.cpp(8): could be 'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)' 
1>   with 
1>   [ 
1>    _Ty=int, 
1>    TContainer=std::vector<int>, 
1>    _Fty=bool (const int &,int) 
1>   ] 
1>   c:\users\moswald\test.cpp(5): or  'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)' 
1>   with 
1>   [ 
1>    _Ty=int, 
1>    TContainer=std::vector<int>, 
1>    _Fty=bool (const int &) 
1>   ] 
1>   while trying to match the argument list '(std::vector<_Ty>, foo::<lambda_8EADDE04A8D35A3C>)' 
1>   with 
1>   [ 
1>    _Ty=int 
1>   ] 
1>c:\users\moswald\test.cpp(19): error C2668: 'DoSomethingIf' : ambiguous call to overloaded function 
1>   c:\users\moswald\test.cpp(8): could be 'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)' 
1>   with 
1>   [ 
1>    _Ty=int, 
1>    TContainer=std::vector<int>, 
1>    _Fty=bool (const int &,int) 
1>   ] 
1>   c:\users\moswald\test.cpp(5): or  'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)' 
1>   with 
1>   [ 
1>    _Ty=int, 
1>    TContainer=std::vector<int>, 
1>    _Fty=bool (const int &) 
1>   ] 
1>   while trying to match the argument list '(std::vector<_Ty>, foo::<lambda_8EADDE04A8D35A3D>)' 
1>   with 
1>   [ 
1>    _Ty=int 
1>   ] 

क्या इसकी उम्मीद की जानी चाहिए थी? वहाँ इन कार्यों (एक का नाम बदलने "DoSomethingIfWithIndex" होने के लिए की कमी ओवरलोड एक अलग तरह?

उत्तर

12

अधिभार अस्पष्टता की उम्मीद है है।

std::function है कि किसी भी तर्क को स्वीकार करता है एक परिवर्तित निर्माता टेम्पलेट है। के बाद ही निर्माता टेम्पलेट तत्काल है कि संकलक निर्धारित करता है कि यह तर्क को अस्वीकार कर देगा।

आपके पहले और दूसरे उदाहरणों में, उपयोगकर्ता द्वारा परिभाषित रूपांतरण को अनिर्दिष्ट लैम्ब्डा प्रकार को std::function प्रकारों में से प्रत्येक में परिवर्तित करने की आवश्यकता है। न तो रूपांतरण बेहतर है (वे उपयोगकर्ता द्वारा परिभाषित रूपांतरण दोनों हैं), इसलिए संकलक ओवरलोड एंबिगुई की रिपोर्ट करता है Ty।

आपके तीसरे उदाहरण (जो काम करता है) में, कोई अस्पष्टता नहीं है क्योंकि std::function कन्स्ट्रक्टर टेम्पलेट का उपयोग नहीं किया जाता है। इसके बजाए, इसकी प्रतिलिपि कन्स्ट्रक्टर का उपयोग किया जाता है (और, अन्य सभी चीजें बराबर होती हैं, टेम्पलेट्स पर नॉनटेम्प्लेट को प्राथमिकता दी जाती है)।

+0

भावना है, लेकिन निराशाजनक बनाता है। ऐसा लगता है कि अलग-अलग लैम्ब्स के माध्यम से ओवरलोडेड फ़ंक्शन का चयन करने का कोई तरीका नहीं है। – moswald

+0

'std :: function' का उपयोग नहीं कर रहा, नहीं। आप कॉल करने योग्य गुणों का उपयोग कर सकते हैं या कैप्चरलेस लैम्बडास के लिए फ़ंक्शन पॉइंटर क्षय पर भरोसा कर सकते हैं, लेकिन आम तौर पर यह बट में दर्द होता है। –

+0

इस तरह कुछ हल करने के लिए कॉल करने योग्य लक्षणों का उपयोग करने के बारे में कोई कैसे होगा? यदि संभव हो, तो मैं किसी भी फ़ंक्शन ऑब्जेक्ट को पास करने में सक्षम होना पसंद करूंगा जो हस्ताक्षर से मेल खाता है, न केवल मुफ्त फ़ंक्शंस/लैम्बडास। – moswald

5

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

template<typename TContainer, typename Predicate> 
void DoSomethingIf(TContainer& c, Predicate&& predicate); 

आप हालांकि देख सकते हैं के रूप में, इस संस्करण अतिभारित नहीं किया जा सकता और केवल कुछ भी स्वीकार करेंगे एक अनुमान के रूप में, यहां तक ​​कि int। ओवरलोडिंग आसानी से SFINAE के साथ हल किया है, हमेशा की तरह:

template< 
    typename Container 
    , typename Predicate 
    , typename = typename std::enable_if< 
     is_callable<Predicate, bool(typename Container::const_reference)>::value 
    >::type 
> 
void 
DoSomethingIf(Container& container, Predicate&& predicate); 

template< 
    typename Container 
    , typename Predicate 
    , typename = typename std::enable_if< 
     is_callable<Predicate, bool(typename Container::const_reference, int)>::value 
    >::type 
    // dummy parameter to disambiguate this definition from the previous one 
    , typename = void 
> 
void 
DoSomethingIf(Container& container, Predicate&& predicate); 

यह अभी भी कष्टप्रद मुद्दा यह है कि अगर किसी को (या वास्तव में कुछ भी) एक विधेय गुजरता है कि हमारे शर्तों को पूरा नहीं करता है, हम एक 'कोई मिलता-जुलता समारोह मिल पाया है एक सहायक त्रुटि के बजाय त्रुटि (एक अधिभार संकल्प विफलता)। आपको लगता है कि हल करना चाहते हैं, तो आप एक 'कैच-ऑल' अधिभार जोड़ सकते हैं:

template< 
    typename Container 
    , typename Predicate 
    , typename = typename std::enable_if< 
     !is_callable<Predicate, bool(typename Container::const_reference)>::value 
     && !is_callable<Predicate, bool(typename Container::const_reference, int)>::value 
    >::type 
    // more dummies 
    , typename = void, typename = void 
> 
void DoSomethingIf(Container&, Predicate&&) 
{ static_assert(dependent_false_type<Container>::value, 
    "Put useful error message here"); } 

(dependent_false_type बस जैसे एक प्रकार std::false_type से इनहेरिट, हम नहीं static_assert बस false पर कर सकते हैं या कि हो जाएगा हो गया है हर बार, न केवल जब टेम्पलेट तत्काल होता है, जैसा कि हम चाहते हैं। वैकल्पिक रूप से आप हमारे पास std::enable_if के अंदर की स्थिति को दोहरा सकते हैं, जो कोड के अंदर दस्तावेज़ के रूप में कुछ हद तक काम करता है, लेकिन कार्यक्षमता में सुधार नहीं करता है।)

यह सब बनी हुई है जहां is_callable<Functor, Signature> , क्योंकि यह वास्तव में एक मानक विशेषता नहीं है।यदि आपने कभी पहले एक SFINAE परीक्षण लिखा है, तो इसे लागू करना अपेक्षाकृत आसान है, लेकिन थोड़ी थकाऊ है क्योंकि आपको void रिटर्न के लिए आंशिक रूप से विशेषज्ञता देना है। मैं यहां एक विशेषज्ञता नहीं डाल रहा हूं क्योंकि यह उत्तर काफी लंबा है।

आप इस समाधान शक्तिशाली लेकिन बहुत वर्बोज़ मिल जाए, तो शायद आप अवधारणाओं का आनंद चाहते हैं :)

+0

धन्यवाद। हाँ, मैंने 'टाइप_ट्रेट्स' हेडर को देखकर कुछ समय बिताया है जिसमें टेम्पलेट्स का संयोजन खोजने की कोशिश की जा रही है जो मुझे 'is_callable' के बराबर देगी। मैं खुद को एक लिखने के लिए निश्चित रूप से खुश हूं, क्योंकि यह पूरी बात मूल रूप से मेरे सी ++ कौशल को तेज रखने के लिए एक अभ्यास थी। – moswald

+0

यह एक साइड प्वाइंट है, लेकिन फ़ंक्शन टेम्पलेट्स को अलग करने के लिए अतिरिक्त डमी टेम्पलेट पैरामीटर पेश करने से बचने के लिए एक तरीका है जो केवल उनकी सक्षम-स्थिति द्वारा भिन्न होता है, 'टाइपनाम सक्षम_आईफ़ :: प्रकार * = 0' पैरामीटर का उपयोग करना है एक 'typename = typename enable_if :: टाइप' पैरामीटर। जब तक कि स्थितियां अन्य टेम्पलेट पैरामीटर पर निर्भर हों, तब तक कंपाइलर को 'enable_if :: टाइप' और 'enable_if :: टाइप' को संभावित रूप से अलग-अलग प्रकारों पर विचार करना चाहिए, और इस प्रकार दो फ़ंक्शन टेम्पलेट अलग-अलग होने चाहिए। – HighCommander4

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