फ़ंक्शन पॉइंटर के रूप में लैम्ब्डा को कैप्चर करना


210

क्या एक फ़ंक्शन पॉइंटर के रूप में लैम्बडा फ़ंक्शन को पास करना संभव है? यदि हां, तो मुझे कुछ गलत करना चाहिए क्योंकि मुझे एक संकलन त्रुटि हो रही है।

निम्नलिखित उदाहरण पर विचार करें

using DecisionFn = bool(*)();

class Decide
{
public:
    Decide(DecisionFn dec) : _dec{dec} {}
private:
    DecisionFn _dec;
};

int main()
{
    int x = 5;
    Decide greaterThanThree{ [x](){ return x > 3; } };
    return 0;
}

जब मैं इसे संकलित करने का प्रयास करता हूं, तो मुझे निम्नलिखित संकलन त्रुटि मिलती है:

In function 'int main()':
17:31: error: the value of 'x' is not usable in a constant expression
16:9:  note: 'int x' is not const
17:53: error: no matching function for call to 'Decide::Decide(<brace-enclosed initializer list>)'
17:53: note: candidates are:
9:5:   note: Decide::Decide(DecisionFn)
9:5:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'DecisionFn {aka bool (*)()}'
6:7:   note: constexpr Decide::Decide(const Decide&)
6:7:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'const Decide&'
6:7:   note: constexpr Decide::Decide(Decide&&)
6:7:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'Decide&&'

यह पचाने के लिए एक त्रुटि संदेश की एक बिल्ली है, लेकिन मुझे लगता है कि मुझे जो मिल रहा है, वह यह है कि लैम्ब्डा को इलाज नहीं किया जा सकता है constexprइसलिए मैं इसे फ़ंक्शन पॉइंटर के रूप में पारित नहीं कर सकता हूं? मैंने xकास्ट बनाने की कोशिश की है , लेकिन वह मदद नहीं करता है।


34
लैम्ब्डा केवल सूचक को कार्य करने के लिए क्षय कर सकता है यदि वे कुछ भी कैप्चर नहीं करते हैं।
Jarod42


जवाबों:


205

एक लैम्ब्डा केवल एक फ़ंक्शन पॉइंटर में परिवर्तित किया जा सकता है यदि वह कैप्चर नहीं करता है, ड्राफ्ट C ++ 11 मानक अनुभाग से 5.1.2 [expr.prim.lambda] कहता है ( जोर मेरा ):

लैम्ब्डा-एक्सप्रेशन के साथ लैम्बडा-एक्सप्रेशन के लिए क्लोजर टाइप में एक पब्लिक नॉन-वर्चुअल नॉन-क्लियर कॉन्स कन्वर्सेशन फंक्शन है, जो पॉइंटर के फंक्शन कॉल ऑपरेटर के रूप में एक ही पैरामीटर और रिटर्न टाइप करने के लिए पॉइंटर करता है । इस रूपांतरण फ़ंक्शन द्वारा लौटाया गया मान किसी फ़ंक्शन का पता होगा, जिसे लागू करते समय, क्लोजर प्रकार के फ़ंक्शन कॉल ऑपरेटर को लागू करने के समान प्रभाव पड़ता है।

ध्यान दें, cppreference लैंबडा फ़ंक्शंस पर अपने सेक्शन में इसे शामिल करता है

तो निम्नलिखित विकल्प काम करेंगे:

typedef bool(*DecisionFn)(int);

Decide greaterThanThree{ []( int x ){ return x > 3; } };

और ऐसा होगा:

typedef bool(*DecisionFn)();

Decide greaterThanThree{ [](){ return true ; } };

और जैसा कि 5gon12eder बताता है, आप भी उपयोग कर सकते हैं std::function, लेकिन ध्यान दें कि std::functionयह भारी वजन है , इसलिए यह कम लागत वाला ट्रेड-ऑफ नहीं है।


2
साइड नोट: सी सामान द्वारा उपयोग किया जाने वाला एक सामान्य समाधान void*एकमात्र पैरामीटर के रूप में पास करना है। इसे आम तौर पर "उपयोगकर्ता सूचक" कहा जाता है। यह अपेक्षाकृत हल्का है, भी, लेकिन इसके लिए आवश्यक है कि आप mallocकुछ स्थान बाहर करें।
निधि मोनिका का मुकदमा

94

शफीक यघमौर का जवाब सही ढंग से बताता है कि अगर लंबोदर को एक कब्जा सूचक के रूप में पारित नहीं किया जा सकता है, तो यह एक कब्जा है। मैं समस्या के लिए दो सरल सुधार दिखाना चाहूंगा।

  1. std::functionकच्चे फ़ंक्शन पॉइंटर्स के बजाय का उपयोग करें ।

    यह एक बहुत साफ समाधान है। ध्यान दें कि यह प्रकार के क्षरण के लिए कुछ अतिरिक्त ओवरहेड भी शामिल है (शायद एक आभासी फ़ंक्शन कॉल)।

    #include <functional>
    #include <utility>
    
    struct Decide
    {
      using DecisionFn = std::function<bool()>;
      Decide(DecisionFn dec) : dec_ {std::move(dec)} {}
      DecisionFn dec_;
    };
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree { [x](){ return x > 3; } };
    }
  2. एक लैम्ब्डा अभिव्यक्ति का उपयोग करें जो कुछ भी कैप्चर नहीं करता है।

    चूँकि आपका विधेय वास्तव में सिर्फ एक बूलियन स्थिरांक है, इसलिए निम्न वर्तमान मुद्दे के आसपास जल्दी काम करेगा। एक अच्छा स्पष्टीकरण क्यों और कैसे काम कर रहा है, इस उत्तर को देखें ।

    // Your 'Decide' class as in your post.
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree {
        (x > 3) ? [](){ return true; } : [](){ return false; }
      };
    }

4
@TC विवरण के लिए इस प्रश्न को देखें कि यह क्यों काम करता है
शाफिक याघमोर

ध्यान दें कि सामान्य रूप से, यदि आप संकलित समय पर कैप्चर डेटा जानते हैं, तो आप इसे टाइप डेटा में बदल सकते हैं और फिर आप बिना किसी कैप्चर के लैम्बडा कर सकते हैं - यह उत्तर देखें कि मैंने सिर्फ एक अन्य प्रश्न के लिए लिखा था (धन्यवाद के लिए @ 5gon12eder का जवाब यहां)।
डैन-मैन

क्या ऑब्जेक्ट को पॉइंटर फ़ंक्शन की तुलना में लंबा जीवनकाल नहीं होना चाहिए? मैं इसके लिए उपयोग करना चाहूंगा glutReshapeFunc
२०१२

मैं इस सुझाव की सिफारिश नहीं करता हूं, जो चीजें जादुई रूप से काम करती हैं, नई त्रुटियों को पेश करती हैं। और अभ्यास जो उन त्रुटियों के साथ जाते हैं। यदि आप std :: function का उपयोग करना चाहते हैं, तो आपको सभी प्रकार के तरीकों को देखना चाहिए जो std :: function का उपयोग कर सकते हैं। क्योंकि कुछ तरीके शायद कुछ ऐसा है जो आप नहीं चाहते हैं।
द नैगेटिव जूल

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

40

लैम्ब्डा के भाव, यहां तक ​​कि कैप्चर किए गए, एक फ़ंक्शन पॉइंटर (सदस्य फ़ंक्शन के लिए पॉइंटर) के रूप में संभाला जा सकता है।

यह मुश्किल है क्योंकि एक लंबोदर अभिव्यक्ति एक साधारण कार्य नहीं है। यह वास्तव में एक ऑपरेटर () के साथ एक वस्तु है।

जब आप रचनात्मक हों, तो आप इसका उपयोग कर सकते हैं! Std :: function की शैली में "फ़ंक्शन" वर्ग के बारे में सोचें। यदि आप ऑब्जेक्ट को सेव करते हैं तो आप फंक्शन पॉइंटर का भी उपयोग कर सकते हैं।

फ़ंक्शन पॉइंटर का उपयोग करने के लिए, आप निम्न का उपयोग कर सकते हैं:

int first = 5;
auto lambda = [=](int x, int z) {
    return x + z + first;
};
int(decltype(lambda)::*ptr)(int, int)const = &decltype(lambda)::operator();
std::cout << "test = " << (lambda.*ptr)(2, 3) << std::endl;

एक वर्ग बनाने के लिए जो "std :: function" की तरह काम करना शुरू कर सकता है, पहले आपको ऑब्जेक्ट और फंक्शन पॉइंटर को स्टोर करने की तुलना में एक क्लास / स्ट्रक्चर की आवश्यकता होती है। इसे निष्पादित करने के लिए आपको एक ऑपरेटर () की आवश्यकता है:

// OT => Object Type
// RT => Return Type
// A ... => Arguments
template<typename OT, typename RT, typename ... A>
struct lambda_expression {
    OT _object;
    RT(OT::*_function)(A...)const;

    lambda_expression(const OT & object)
        : _object(object), _function(&decltype(_object)::operator()) {}

    RT operator() (A ... args) const {
        return (_object.*_function)(args...);
    }
};

इसके साथ अब आप कैप्चर किए गए, गैर-कैप्चर किए गए लैम्ब्डा चला सकते हैं, जैसे आप मूल का उपयोग कर रहे हैं:

auto capture_lambda() {
    int first = 5;
    auto lambda = [=](int x, int z) {
        return x + z + first;
    };
    return lambda_expression<decltype(lambda), int, int, int>(lambda);
}

auto noncapture_lambda() {
    auto lambda = [](int x, int z) {
        return x + z;
    };
    return lambda_expression<decltype(lambda), int, int, int>(lambda);
}

void refcapture_lambda() {
    int test;
    auto lambda = [&](int x, int z) {
        test = x + z;
    };
    lambda_expression<decltype(lambda), void, int, int>f(lambda);
    f(2, 3);

    std::cout << "test value = " << test << std::endl;
}

int main(int argc, char **argv) {
    auto f_capture = capture_lambda();
    auto f_noncapture = noncapture_lambda();

    std::cout << "main test = " << f_capture(2, 3) << std::endl;
    std::cout << "main test = " << f_noncapture(2, 3) << std::endl;

    refcapture_lambda();

    system("PAUSE");
    return 0;
}

यह कोड VS2015 के साथ काम करता है

अद्यतन 04.07.17:

template <typename CT, typename ... A> struct function
: public function<decltype(&CT::operator())(A...)> {};

template <typename C> struct function<C> {
private:
    C mObject;

public:
    function(const C & obj)
        : mObject(obj) {}

    template<typename... Args> typename 
    std::result_of<C(Args...)>::type operator()(Args... a) {
        return this->mObject.operator()(a...);
    }

    template<typename... Args> typename 
    std::result_of<const C(Args...)>::type operator()(Args... a) const {
        return this->mObject.operator()(a...);
    }
};

namespace make {
    template<typename C> auto function(const C & obj) {
        return ::function<C>(obj);
    }
}

int main(int argc, char ** argv) {
   auto func = make::function([](int y, int x) { return x*y; });
   std::cout << func(2, 4) << std::endl;
   system("PAUSE");
   return 0;
}

वाह कमाल है! तो हम सिर्फ लैम्ब्डा के क्लास के इनर पॉइंटर्स (मेंबर फंक्शन ऑपरेटर ()) को रैपर क्लास में स्टोर किए गए लैम्ब्डा का इस्तेमाल करने के लिए इस्तेमाल कर सकते हैं !! गजब का!! हमें कभी भी std :: function की आवश्यकता क्यों है? और क्या लैम्ब्डा_एक्सप्रेशन बनाना संभव है <घोषणापत्र (लैम्ब्डा), इंट, इंट, इंट, इंट> स्वचालित रूप से कम करने के लिए / इन "इंट" पैरामीटर सीधे लैम्बडा से ही?
बार्नी

2
मैंने अपने कोड का एक छोटा संस्करण जोड़ा है। यह एक साधारण ऑटो के साथ काम करना चाहिए f = make :: function (lambda); लेकिन मैं काफी shure हूँ, आपको बहुत सारी स्थिति मिलेगी मेरा कोड काम नहीं करेगा। std :: फंक्शन इस तरह से ज्यादा अच्छी तरह से बनाया गया है और जब आप काम कर रहे हों तब यह जाना चाहिए। यह यहां शिक्षा और व्यक्तिगत उपयोग के लिए है।
Noxxer

14
इस समाधान में एक operator()कार्यान्वयन के माध्यम से लैम्बडा को कॉल करना शामिल है , इसलिए अगर मैं इसे सही पढ़ रहा हूं तो मुझे नहीं लगता कि यह सी-स्टाइल फ़ंक्शन पॉइंटर का उपयोग करके लैम्ब्डा को कॉल करने के लिए काम करेगा, क्या यह होगा? यही मूल प्रश्न पूछा गया है।
रेमी लेबेऊ

13
आपने दावा किया कि लंबोदर को फंक्शन पॉइंटर्स के रूप में संभाला जा सकता है, जो आपने नहीं किया। आपने लैम्ब्डा रखने के लिए एक और ऑब्जेक्ट बनाया, जो कुछ भी नहीं करता है, आप सिर्फ मूल लैम्ब्डा का उपयोग कर सकते हैं।
राहगीर 1

9
यह "फंक्शन पॉइंटर के रूप में लैम्ब्डा कैप्चरिंग पासिंग" नहीं है। यह "लैम्ब्डा को एक वस्तु के रूप में कैप्चर करना है जिसमें अन्य चीजों के बीच एक फ़ंक्शन पॉइंटर होता है"। अंतर की दुनिया है।
एन। 'सर्वनाम' मी।

15

लंबर कैप्चरिंग को फ़ंक्शन पॉइंटर्स में परिवर्तित नहीं किया जा सकता है, क्योंकि यह उत्तर इंगित करता है।

हालांकि, यह अक्सर एक एपीआई के लिए फ़ंक्शन पॉइंटर की आपूर्ति करने के लिए काफी दर्द होता है जो केवल एक को स्वीकार करता है। ऐसा करने के लिए सबसे अक्सर उद्धृत पद्धति एक फ़ंक्शन प्रदान करने और इसके साथ एक स्थिर ऑब्जेक्ट को कॉल करने के लिए है।

static Callable callable;
static bool wrapper()
{
    return callable();
}

यह थकाऊ है। हम इस विचार को और आगे ले जाते हैं wrapperऔर जीवन को आसान बनाने और बनाने की प्रक्रिया को स्वचालित करते हैं ।

#include<type_traits>
#include<utility>

template<typename Callable>
union storage
{
    storage() {}
    std::decay_t<Callable> callable;
};

template<int, typename Callable, typename Ret, typename... Args>
auto fnptr_(Callable&& c, Ret (*)(Args...))
{
    static bool used = false;
    static storage<Callable> s;
    using type = decltype(s.callable);

    if(used)
        s.callable.~type();
    new (&s.callable) type(std::forward<Callable>(c));
    used = true;

    return [](Args... args) -> Ret {
        return Ret(s.callable(std::forward<Args>(args)...));
    };
}

template<typename Fn, int N = 0, typename Callable>
Fn* fnptr(Callable&& c)
{
    return fnptr_<N>(std::forward<Callable>(c), (Fn*)nullptr);
}

और इसका उपयोग करें

void foo(void (*fn)())
{
    fn();   
}

int main()
{
    int i = 42;
    auto fn = fnptr<void()>([i]{std::cout << i;});
    foo(fn);  // compiles!
}

लाइव

यह अनिवार्य रूप से प्रत्येक घटना पर एक अनाम फ़ंक्शन घोषित कर रहा है fnptr

ध्यान दें कि एक ही प्रकार fnptrके पहले से callableदिए गए कॉलबल्स को अधिलेखित करने के आह्वान । हम इसे एक निश्चित डिग्री तक, intपैरामीटर के साथ मापते हैं N

std::function<void()> func1, func2;
auto fn1 = fnptr<void(), 1>(func1);
auto fn2 = fnptr<void(), 2>(func2);  // different function

एन पूर्णांक को घोषित करने के लिए मजबूर करना क्लाइंट को याद करने का एक शानदार तरीका होगा कि संकलन समय पर फ़ंक्शन पॉइंटर्स को ओवरराइट करने से बचें।
fiorentinoing

2

C फ़ंक्शन पॉइंटर के रूप में लैम्बडा का उपयोग करने का शॉर्टकट यह है:

"auto fun = +[](){}"

कर्ल का उपयोग एक्सफॉम्पल के रूप में करना ( कर्ल डीबग जानकारी )

auto callback = +[](CURL* handle, curl_infotype type, char* data, size_t size, void*){ //add code here :-) };
curl_easy_setopt(curlHande, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curlHande,CURLOPT_DEBUGFUNCTION,callback);

3
उस मेमने के पास कब्जा नहीं है। ओपी का मुद्दा कैप्चर है, फ़ंक्शन पॉइंटर प्रकार (जो कि +चाल आपको मिलती है) को कम करने के लिए नहीं।
स्नेफेल

2

जबकि टेम्पलेट दृष्टिकोण विभिन्न कारणों से चालाक है, लेकिन लैम्ब्डा और कैप्चर किए गए चर के जीवनचक्र को याद रखना महत्वपूर्ण है। यदि लैम्ब्डा पॉइंटर के किसी भी रूप का उपयोग किया जा रहा है और लैम्ब्डा नीचे की ओर नहीं है, तो केवल एक कॉपी [=] लैम्ब्डा का उपयोग किया जाना चाहिए। यानी, तब भी, स्टैक पर एक वेरिएबल को एक पॉइंटर को कैप्चर करना UNSAFE है यदि उन कैप्चर किए गए पॉइंटर्स (स्टैक लेयर) का जीवनकाल लैम्बडा के जीवनकाल से कम है।

एक लंबर को पॉइंटर के रूप में कैप्चर करने का एक सरल उपाय है:

auto pLamdba = new std::function<...fn-sig...>([=](...fn-sig...){...});

जैसे, new std::function<void()>([=]() -> void {...}

बस बाद में याद रखें delete pLamdbaताकि यह सुनिश्चित हो सके कि आप मेमने की मेमोरी को लीक न करें। यहां महसूस करने का रहस्य यह है कि लैम्ब्डा लैम्ब्डा (खुद से पूछें कि यह कैसे काम करता है) पर कब्जा कर सकता है और यह भी कि std::functionउदारता से काम करने के लिए लैम्ब्डा के कार्यान्वयन में लैम्बडा (और कैप्चर किए गए) डेटा () पर पहुंच प्रदान करने के लिए पर्याप्त आंतरिक जानकारी होनी चाहिए। यही कारण है कि deleteकाम करना चाहिए [पर कब्जा कर लिया प्रकार के विनाशकारी]]।


क्यों परेशान new- std :: function पहले से ही lambda को ढेर पर स्टोर करता है और कॉल डिलीट को याद रखने की जरूरत से बचता है।
क्रिस डोड

0

एक सीधा जवाब नहीं है, लेकिन लैम्ब्डा प्रकार की बारीकियों को छिपाने के लिए "फ़ंक्टर" टेम्पलेट पैटर्न का उपयोग करने के लिए थोड़ी भिन्नता है और कोड को अच्छा और सरल रखता है।

मुझे यकीन नहीं था कि आप निर्णय वर्ग का उपयोग कैसे करना चाहते हैं, इसलिए मुझे एक फ़ंक्शन के साथ कक्षा का विस्तार करना होगा जो इसका उपयोग करता है। पूरा उदाहरण यहां देखें: https://godbolt.org/z/jtByqE

आपकी कक्षा का मूल रूप इस तरह दिख सकता है:

template <typename Functor>
class Decide
{
public:
    Decide(Functor dec) : _dec{dec} {}
private:
    Functor _dec;
};

जहाँ आप उपयोग किए गए वर्ग प्रकार के हिस्से में फ़ंक्शन के प्रकार को पास करते हैं:

auto decide_fc = [](int x){ return x > 3; };
Decide<decltype(decide_fc)> greaterThanThree{decide_fc};

फिर, मुझे यकीन नहीं था कि आप xइसे क्यों कैप्चर कर रहे हैं, इससे अधिक समझ में आता है (मेरे लिए) एक पैरामीटर है जिसे आप लैम्ब्डा में पास करते हैं) ताकि आप उपयोग कर सकें:

int result = _dec(5); // or whatever value

एक पूर्ण उदाहरण के लिए लिंक देखें


-2

जैसा कि दूसरों ने बताया था कि आप फ़ंक्शन पॉइंटर के बजाय लैंबडा फ़ंक्शन को स्थानापन्न कर सकते हैं। मैं अपने C ++ इंटरफ़ेस में F77 ODE सॉल्वर RKSUITE में इस विधि का उपयोग कर रहा हूं।

//C interface to Fortran subroutine UT
extern "C"  void UT(void(*)(double*,double*,double*),double*,double*,double*,
double*,double*,double*,int*);

// C++ wrapper which calls extern "C" void UT routine
static  void   rk_ut(void(*)(double*,double*,double*),double*,double*,double*,
double*,double*,double*,int*);

//  Call of rk_ut with lambda passed instead of function pointer to derivative
//  routine
mathlib::RungeKuttaSolver::rk_ut([](double* T,double* Y,double* YP)->void{YP[0]=Y[1]; YP[1]= -Y[0];}, TWANT,T,Y,YP,YMAX,WORK,UFLAG);
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.