2010-04-01 18 views
22

में लैम्ब्डा पर टेम्पलेट को विशेषज्ञता देना मैंने एक विशेषता वर्ग लिखा है जो मुझे तर्कों और सी ++ 0x में फ़ंक्शन या फ़ंक्शन ऑब्जेक्ट के प्रकार (जीसीसी 4.5.0 के साथ परीक्षण) के बारे में जानकारी निकालने देता है। ।सी ++ 0x

template <typename F> 
struct function_traits { 
    template <typename R, typename... A> 
    struct _internal { }; 

    template <typename R, typename... A> 
    struct _internal<R (F::*)(A...)> { 
     // ... 
    }; 

    typedef typename _internal<decltype(&F::operator())>::<<nested types go here>>; 
}; 

तब मैं वैश्विक क्षेत्र में सादा कार्यों के लिए एक विशेषज्ञता है:

template <typename R, typename... A> 
struct function_traits<R (*)(A...)> { 
    // ... 
}; 

यह ठीक काम करता है, मैं टेम्पलेट या एक समारोह वस्तु और में एक समारोह पारित कर सकते हैं सामान्य मामले समारोह वस्तुओं संभालती यह ठीक से काम करता है:

template <typename F> 
void foo(F f) { 
    typename function_traits<F>::whatever ...; 
} 

int f(int x) { ... } 
foo(f); 

क्या होगा अगर, बजाय foo में एक समारोह या समारोह वस्तु पास करने का, मैं एक लैम्ब्डा अभिव्यक्ति पास करना चाहते हैं?

foo([](int x) { ... }); 

समस्या यह है कि न तो function_traits<> का विशेषज्ञता लागू होता है। सी ++ 0 एक्स ड्राफ्ट का कहना है कि अभिव्यक्ति का प्रकार "अद्वितीय, अनामित, गैर-यूनियन क्लास प्रकार" है। अभिव्यक्ति पर typeid(...).name() पर कॉल करने के नतीजे को झुकाव मुझे लैम्ब्डा, main::{lambda(int)#1} के लिए जीसीसी के आंतरिक नामकरण सम्मेलन के रूप में दिखाई देता है, ऐसा कुछ नहीं जो सिंटैक्टिक रूप से सी ++ टाइपनाम का प्रतिनिधित्व करता है।

संक्षेप में, वहाँ कुछ भी मैं टेम्पलेट यहाँ में डाल सकता है:

template <typename R, typename... A> 
struct function_traits<????> { ... } 

कि इस लक्षण वर्ग एक लैम्ब्डा अभिव्यक्ति को स्वीकार करने की अनुमति देगा?

+0

नहीं। आपको ऐसा क्यों लगता है कि आपको ऐसा कुछ चाहिए? – sellibitze

+1

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

+0

@ टोनी: जवाब हाँ है, मैंने इसे किया है। हालांकि, मैं थोड़ी देर बाद इस सवाल पर वापस आ पाऊंगा। आप क्या गुण प्राप्त करने की कोशिश कर रहे हैं? – GManNickG

उत्तर

17

मुझे लगता है कि लैम्बडास के लिए लक्षणों का विशेषज्ञ होना और अज्ञात मज़ेदार के हस्ताक्षर पर मिलान पैटर्न करना संभव है। यहां कोड है जो g ++ 4.5 पर काम करता है। हालांकि यह काम करता है, लैम्ब्डा पर मिलान पैटर्न पैटर्न अंतर्ज्ञान के विपरीत काम करता प्रतीत होता है। मैंने इनलाइन टिप्पणी की है।

struct X 
{ 
    float operator() (float i) { return i*2; } 
    // If the following is enabled, program fails to compile 
    // mostly because of ambiguity reasons. 
    //double operator() (float i, double d) { return d*f; } 
}; 

template <typename T> 
struct function_traits // matches when T=X or T=lambda 
// As expected, lambda creates a "unique, unnamed, non-union class type" 
// so it matches here 
{ 
    // Here is what you are looking for. The type of the member operator() 
    // of the lambda is taken and mapped again on function_traits. 
    typedef typename function_traits<decltype(&T::operator())>::return_type return_type; 
}; 

// matches for X::operator() but not of lambda::operator() 
template <typename R, typename C, typename... A> 
struct function_traits<R (C::*)(A...)> 
{ 
    typedef R return_type; 
}; 

// I initially thought the above defined member function specialization of 
// the trait will match lambdas::operator() because a lambda is a functor. 
// It does not, however. Instead, it matches the one below. 
// I wonder why? implementation defined? 
template <typename R, typename... A> 
struct function_traits<R (*)(A...)> // matches for lambda::operator() 
{ 
    typedef R return_type; 
}; 

template <typename F> 
typename function_traits<F>::return_type 
foo(F f) 
{ 
    return f(10); 
} 

template <typename F> 
typename function_traits<F>::return_type 
bar(F f) 
{ 
    return f(5.0f, 100, 0.34); 
} 

int f(int x) { return x + x; } 

int main(void) 
{ 
    foo(f); 
    foo(X()); 
    bar([](float f, int l, double d){ return f+l+d; }); 
} 
+0

धन्यवाद, यह चाल है। ऐसा नहीं हुआ कि इस तरह के विशेष संस्करण पर नॉन-स्पेशल टेम्पलेट को सिर्फ चेन करने के लिए। एक बार मैंने इसे देखा, यह अविश्वसनीय रूप से सुरुचिपूर्ण और स्पष्ट दिखता है। –

+0

मुझे खुशी है कि इससे मदद मिली। ऐसा लगता है कि लैम्ब्डा के ऑपरेटर() में प्राप्त होने का सिंटैक्स कक्षा के सदस्य फ़ंक्शन की तरह है, लैम्ब्डा बाद में एक अज्ञात मुक्त स्थायी कार्य है और आर (*) (ए ...) से मेल खाता है लेकिन आर (सी :: *)(ए...)। – Sumant

+2

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

1

समारोह टेम्पलेट्स एक वर्ग टेम्पलेट के बजाय की एक श्रृंखला के लिए काम के कुछ सौंपकर आप प्रासंगिक जानकारी निकाल सकते हैं।

सबसे पहले, मुझे यह कहना चाहिए कि प्रासंगिक विधि const विधि है, एक लैम्ब्डा के लिए (गैर-कैप्चरिंग, गैर-जेनेरिक, गैर-mutable लैम्ब्डा के लिए)। तो अगर आप एक सच्चे लैम्ब्डा के बीच का अंतर है और इस बताने के लिए सक्षम नहीं होगा: इसलिए

struct { 
    int operator() (int) const { return 7; } 
} object_of_unnamed_name_and_with_suitable_method; 

, मुझे लगता है होना चाहिए कि आप नहीं करना चाहते हैं "विशेष उपचार" lambdas के लिए, और आप परीक्षण करने के लिए नहीं करना चाहते हैं यदि एक प्रकार एक लैम्ब्डा प्रकार है, और इसके बजाय आप किसी भी ऑब्जेक्ट के लिए पर्याप्त आसानी से वापसी प्रकार, और सभी तर्कों के प्रकार को निकालना चाहते हैं। "सरल सरल" से मेरा मतलब है, उदाहरण के लिए, operator() विधि स्वयं एक टेम्पलेट नहीं है। और, बोनस की जानकारी के लिए, एक बूलियन हमें यह बताने के लिए कि operator() विधि मौजूद थी और एक सादे पुराने फ़ंक्शन के विपरीत, उपयोग किया गया था।



// First, a convenient struct in which to store all the results: 
template<bool is_method_, bool is_const_method_, typename C, typename R, typename ...Args> 
struct function_traits_results { 
    constexpr static bool is_method = is_method_; 
    constexpr static bool is_const_method = is_const_method_; 
    typedef C class_type; // void for plain functions. Otherwise, 
          // the functor/lambda type 
    typedef R return_type; 
    typedef tuple<Args...> args_type_as_tuple; 
}; 

// This will extract all the details from a method-signature: 
template<typename> 
struct intermediate_step; 
template<typename R, typename C, typename ...Args> 
struct intermediate_step<R (C::*) (Args...)> // non-const methods 
    : public function_traits_results<true, false, C, R, Args...> 
{ 
}; 
template<typename R, typename C, typename ...Args> 
struct intermediate_step<R (C::*) (Args...) const> // const methods 
    : public function_traits_results<true, true, C, R, Args...> 
{ 
}; 


// These next two overloads do the initial task of separating 
// plain function pointers for functors with ::operator() 
template<typename R, typename ...Args> 
function_traits_results<false, false, void, R, Args...> 
function_traits_helper(R (*) (Args...)); 
template<typename F, typename ..., typename MemberType = decltype(&F::operator()) > 
intermediate_step<MemberType> 
function_traits_helper(F); 


// Finally, the actual `function_traits` struct, that delegates 
// everything to the helper 
template <typename T> 
struct function_traits : public decltype(function_traits_helper(declval<T>())) 
{ 
}; 
+0

मैं फ़ंक्शन टेम्पलेट के अंदर इस विशेषता का उपयोग कैसे कर सकता हूं, जो एक std :: function घोषित करने के लिए शून्य func (T lambda) {} जैसे लैम्ब्डा प्राप्त करता है? – barney

2

void_t चाल कर सकते हैं। How does `void_t` work?

जब तक आप है सी ++ 17, आप void_t की परिभाषा शामिल करना होगा:,

template<typename... Ts> struct make_void { typedef void type;}; 
template<typename... Ts> using void_t = typename make_void<Ts...>::type; 

मूल टेम्पलेट के लिए एक अतिरिक्त टेम्पलेट तर्क जोड़े void को चूक:

template <typename T, typename = void> 
struct function_traits; 

सरल कार्यों के लिए गुण वस्तुएं आपके पास पहले से ही हैं:

template <typename R, typename... A> 
struct function_traits<R (*)(A...)> 
{ 
    using return_type = R; 
    using class_type = void; 
    using args_type = std:: tuple<A...>; 
}; 

गैर स्थिरांक तरीकों के लिए:

template <typename R, typename... A> 
struct function_traits<R (*)(A...)> 
{ 
    using return_type = R; 
    using class_type = void; 
    using args_type = std:: tuple<A...>; 
}; 

मत भूलना const तरीके:

template <typename R, typename C, typename... A> 
struct function_traits<R (C::*)(A...) const> // const 
{ 
    using return_type = R; 
    using class_type = C; 
    using args_type = std:: tuple<A...>; 
}; 

अंत में, महत्वपूर्ण विशेषता। लैम्ब्डा प्रकारों सहित कक्षा प्रकार को देखते हुए, हम T से decltype(&T::operator()) पर आगे बढ़ना चाहते हैं। हम यह सुनिश्चित करना चाहते हैं कि यह विशेषता केवल T के लिए उपलब्ध है जिसके लिए ::operator() उपलब्ध है, और यह void_t हमारे लिए है। इस बाधा को लागू करने के लिए हम कहीं विशेषता हस्ताक्षर में &T::operator() डाल करने के लिए है, इसलिए जरूरत template <typename T> struct function_traits<T, void_t< decltype(&T::operator())

template <typename T> 
struct function_traits<T, void_t< decltype(&T::operator()) > > 
: public function_traits<   decltype(&T::operator()) > 
{ 
}; 

में (गैर mutable, गैर सामान्य) lambdas ऑपरेटर() विधि const है, जो बताता है कि क्यों हम की जरूरत है ऊपर const टेम्पलेट।

लेकिन आखिरकार यह बहुत ही सीमित है। यह जेनेरिक लैम्ब्डा, या टेम्पलेट operator() के साथ ऑब्जेक्ट्स के साथ काम नहीं करेगा। यदि आप अपने डिजाइन पर पुनर्विचार करते हैं, तो आपको एक अलग दृष्टिकोण मिल जाता है जो अधिक लचीला है।

+0

गलती 'गैर-आधार विधियों' के तहत। ऐसा लगता है कि आपने मुफ्त फ़ंक्शन को कॉपी किया है। यह <आर (सी :: *) (ए ...)> नीचे एक आधार की तरह हस्ताक्षर होना चाहिए – kert