एक फ़ंक्शन पॉइंटर के रूप में कैप्चर के साथ C ++ लैंबडा


94

मैं फंक्शन पॉइंटर्स में C ++ लैंबडास और उनके निहित रूपांतरण के साथ खेल रहा था। मेरा शुरुआती उदाहरण उन्हें फूट फंक्शन के लिए कॉलबैक के रूप में इस्तेमाल कर रहा था। यह उम्मीद के मुताबिक काम करता है।

#include <ftw.h>
#include <iostream>

using namespace std;

int main()
{
    auto callback = [](const char *fpath, const struct stat *sb,
        int typeflag) -> int {
        cout << fpath << endl;
        return 0;
    };

    int ret = ftw("/etc", callback, 1);

    return ret;
}

कैप्चर का उपयोग करने के लिए इसे संशोधित करने के बाद:

int main()
{

    vector<string> entries;

    auto callback = [&](const char *fpath, const struct stat *sb,
        int typeflag) -> int {
        entries.push_back(fpath);
        return 0;
    };

    int ret = ftw("/etc", callback, 1);

    for (auto entry : entries ) {
        cout << entry << endl;
    }

    return ret;
}

मुझे संकलक त्रुटि मिली:

error: cannot convert main()::<lambda(const char*, const stat*, int)>’ to __ftw_func_t {aka int (*)(const char*, const stat*, int)}’ for argument 2 to int ftw(const char*, __ftw_func_t, int)’

कुछ पढ़ने के बाद। मैंने सीखा है कि कैद का उपयोग करने वाले लंबोदरों को फ़ंक्शन बिंदुओं में परिवर्तित नहीं किया जा सकता है

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


आप किस कंपाइलर का उपयोग कर रहे हैं? क्या यह वीएस 10 है?
रेमन ज़राज़ुआ बी।

gcc संस्करण 4.6.1 20110801 [gcc-4_6-ब्रांच संशोधन 177033] (SUSE लाइनेक्स)
duncan

4
आमतौर पर, कॉलबैक के लिए राज्य पास करने का तरीका कॉलबैक (आमतौर पर प्रकार void *) के लिए एक अतिरिक्त तर्क के माध्यम से किया जाता है । यदि आप जिस लाइब्रेरी का उपयोग कर रहे हैं, वह इस अतिरिक्त तर्क के लिए अनुमति देता है, तो आपको वर्कअराउंड मिलेगा। अन्यथा, आपके पास साफ-साफ हासिल करने का कोई रास्ता नहीं है कि आप क्या करना चाहते हैं।
अलेक्जेंड्रे सी।

हाँ। मुझे एहसास है कि ftw.h और nftw.h की एपी त्रुटिपूर्ण है। मैं
duncan

1
महान! /usr/include/fts.h:41:3: त्रुटि: #error "<fts.h> का उपयोग -D_FILE_OFFSET_BITS == 64" के साथ नहीं किया जा सकता है
duncan

जवाबों:


47

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

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

लेकिन यह लंबोदर को पकड़ने के पूरे उद्देश्य को हराने जैसा है।


3
एक क्लीनर समाधान एक एडेप्टर के अंदर लैम्ब्डा को लपेटना है, यह मानते हुए कि फ़ंक्शन पॉइंटर में एक संदर्भ पैरामीटर है।
रेमंड चेन

4
@ रेमंडचेन: ठीक है, यदि आप यह परिभाषित करने के लिए स्वतंत्र हैं कि फ़ंक्शन का उपयोग कैसे किया जाए, तो हाँ, यह एक विकल्प है। हालांकि उस मामले में यह पैरामीटर को लैंबडा का तर्क बनाने के लिए भी आसान होगा!
केरेक एसबी

3
@KerrekSB ने वैश्विक वैरिएबल को एक में रखा namespaceऔर उन्हें चिह्नित किया thread_local, जैसा कि ftwमैंने कुछ इसी तरह हल करने के लिए चुना था।
केजेल हेडस्ट्रॉम

"एक फ़ंक्शन पॉइंटर एकल, वैश्विक फ़ंक्शन को इंगित करता है, और इस जानकारी में एक राज्य के लिए कोई जगह नहीं है।" -> जावा जैसी भाषाएं इसे कैसे पूरा कर सकती हैं? खैर, निश्चित रूप से, क्योंकि वह एकल, वैश्विक फ़ंक्शन रनटाइम पर बनाया गया है और अपने कोड में राज्य (या इसके संदर्भ ) को एम्बेड करता है । यही कारण है कि है पूरे मुद्दे - वहाँ जाना चाहिए नहीं एक हो ही, वैश्विक समारोह लेकिन कई वैश्विक कार्य - हर बार लैम्ब्डा के लिए एक क्रम में प्रयोग किया जाता है। क्या वास्तव में C ++ में ऐसा कुछ नहीं है जो ऐसा करता है? (मुझे लगा कि std :: फंक्शन बिल्कुल उसी उद्देश्य से बनाया गया है)
Dexter

1
@Dexter: इर्रर .. लघु उत्तर नहीं है, लंबे उत्तर में ऑपरेटर ओवरलोडिंग शामिल है। बावजूद, मेरी बात खड़ी है। जावा एक अलग भाषा है जो C ++ जैसी नहीं है; जावा में पॉइंटर्स (या अधिभार कॉल ऑपरेटर) नहीं हैं और तुलना अच्छी तरह से काम नहीं करती है।
केरेक एसबी

47

मैं बस इस समस्या में भाग गया।

कोड लैम्ब्डा कैप्चर के बिना ठीक संकलित करता है, लेकिन लैम्ब्डा कैप्चर के साथ एक प्रकार का रूपांतरण त्रुटि है।

C ++ 11 के साथ समाधान का उपयोग करना है std::function(संपादित करें: एक और समाधान जिसे फ़ंक्शन हस्ताक्षर को संशोधित करने की आवश्यकता नहीं है, इस उदाहरण के बाद दिखाया गया है)। आप भी उपयोग कर सकते हैं boost::function(जो वास्तव में काफी तेजी से चलता है)। उदाहरण कोड - बदल दिया गया है ताकि यह संकलित हो जाए gcc 4.7.1:

#include <iostream>
#include <vector>
#include <functional>

using namespace std;

int ftw(const char *fpath, std::function<int (const char *path)> callback) {
  return callback(fpath);
}

int main()
{
  vector<string> entries;

  std::function<int (const char *fpath)> callback = [&](const char *fpath) -> int {
    entries.push_back(fpath);
    return 0;
  };

  int ret = ftw("/etc", callback);

  for (auto entry : entries ) {
    cout << entry << endl;
  }

  return ret;
}

संपादित करें: जब मुझे विरासत कोड में भाग दिया गया था, तो मुझे इसे फिर से देखना पड़ा जहां मैं मूल फ़ंक्शन हस्ताक्षर को संशोधित नहीं कर सका, लेकिन फिर भी लैम्ब्डा का उपयोग करने की आवश्यकता थी। एक समाधान जिसे मूल फ़ंक्शन के फ़ंक्शन हस्ताक्षर को संशोधित करने की आवश्यकता नहीं है, वह है:

#include <iostream>
#include <vector>
#include <functional>

using namespace std;

// Original ftw function taking raw function pointer that cannot be modified
int ftw(const char *fpath, int(*callback)(const char *path)) {
  return callback(fpath);
}

static std::function<int(const char*path)> ftw_callback_function;

static int ftw_callback_helper(const char *path) {
  return ftw_callback_function(path);
}

// ftw overload accepting lambda function
static int ftw(const char *fpath, std::function<int(const char *path)> callback) {
  ftw_callback_function = callback;
  return ftw(fpath, ftw_callback_helper);
}

int main() {
  vector<string> entries;

  std::function<int (const char *fpath)> callback = [&](const char *fpath) -> int {
    entries.push_back(fpath);
    return 0;
  };
  int ret = ftw("/etc", callback);

  for (auto entry : entries ) {
    cout << entry << endl;
  }

  return ret;
}

72
नहीं, यह स्वीकृत उत्तर नहीं होना चाहिए। एक फ़ंक्शन पॉइंटर के बजाय ftwलेने के लिए बिंदु नहीं बदल रहा है std::function...
ग्रेगरी पकोस्ज़

इस उत्तर के भीतर प्रस्तावित दूसरा समाधान मूल हस्ताक्षर को संरक्षित करके @ gregory-pakosz से चिंता को संबोधित करता है, लेकिन यह अभी भी महान नहीं है क्योंकि यह वैश्विक स्थिति का परिचय देता है। यदि ftwएक शून्य * userdata तर्क था, तो मैं @ evgeny-karpov से जवाब पसंद करूंगा।
गौरव

@prideout ने सहमति व्यक्त की - मुझे या तो वैश्विक राज्य पसंद नहीं है। दुर्भाग्य से, माना जाता है कि ftw के हस्ताक्षर को संशोधित नहीं किया जा सकता है और यह देखते हुए कि इसमें शून्य * userdata नहीं है, राज्य को कहीं संग्रहीत किया जाना है। मैं 3rd पार्टी लाइब्रेरी का उपयोग करके इस समस्या में भाग गया। यह तब तक ठीक काम करेगा जब तक लाइब्रेरी कॉलबैक को कैप्चर नहीं करती है और बाद में इसका उपयोग करती है, ऐसे में ग्लोबल वैरिएबल कॉल स्टैक पर एक अतिरिक्त पैरामीटर की तरह काम करता है। यदि ftw के हस्ताक्षर को संशोधित किया जा सकता है, तो मैं vd * userdata के बजाय std :: function का उपयोग करना पसंद करूंगा।
जे वेस्ट

1
यह एक अत्यंत जटिल और उपयोगी समाधान है, @Gregory मैं आपको "यह काम करता है" बता देना चाहिए।
fiorentinoing

16

मूल

लैम्ब्डा फ़ंक्शन बहुत सुविधाजनक हैं और एक कोड को कम करते हैं। मेरे मामले में मुझे समानांतर प्रोग्रामिंग के लिए लैम्ब्डा की आवश्यकता थी। लेकिन इसके लिए कैप्चरिंग और फंक्शन पॉइंटर्स की आवश्यकता होती है। मेरा समाधान यहाँ है। लेकिन उन चरों के दायरे से सावधान रहें जिन्हें आपने कैप्चर किया था।

template<typename Tret, typename T>
Tret lambda_ptr_exec(T* v) {
    return (Tret) (*v)();
}

template<typename Tret = void, typename Tfp = Tret(*)(void*), typename T>
Tfp lambda_ptr(T& v) {
    return (Tfp) lambda_ptr_exec<Tret, T>;
}

उदाहरण

int a = 100;
auto b = [&]() { a += 1;};
void (*fp)(void*) = lambda_ptr(b);
fp(&b);

वापसी मूल्य के साथ उदाहरण

int a = 100;
auto b = [&]() {return a;};
int (*fp)(void*) = lambda_ptr<int>(b);
fp(&b);

अपडेट करें

संशोधित संस्करण

फ़ंक्शन पॉइंटर के रूप में कैप्चर के साथ C ++ लैंबडा के बारे में पहली पोस्ट के बाद से यह कुछ समय था। जैसा कि यह मेरे और अन्य लोगों के लिए उपयोगी था, मैंने कुछ सुधार किया।

मानक फ़ंक्शन सी पॉइंटर एपी वॉयड एफएन (शून्य * डेटा) सम्मेलन का उपयोग करता है। डिफ़ॉल्ट रूप से इस सम्मेलन का उपयोग किया जाता है और लैम्ब्डा को एक शून्य * तर्क के साथ घोषित किया जाना चाहिए।

बेहतर क्रियान्वयन

struct Lambda {
    template<typename Tret, typename T>
    static Tret lambda_ptr_exec(void* data) {
        return (Tret) (*(T*)fn<T>())(data);
    }

    template<typename Tret = void, typename Tfp = Tret(*)(void*), typename T>
    static Tfp ptr(T& t) {
        fn<T>(&t);
        return (Tfp) lambda_ptr_exec<Tret, T>;
    }

    template<typename T>
    static void* fn(void* new_fn = nullptr) {
        static void* fn;
        if (new_fn != nullptr)
            fn = new_fn;
        return fn;
    }
};

exapmle

int a = 100;
auto b = [&](void*) {return ++a;};

C सूचक को कैप्चर के साथ लैम्ब्डा परिवर्तित करना

void (*f1)(void*) = Lambda::ptr(b);
f1(nullptr);
printf("%d\n", a);  // 101 

इस तरह से भी इस्तेमाल किया जा सकता है

auto f2 = Lambda::ptr(b);
f2(nullptr);
printf("%d\n", a); // 102

यदि रिटर्न वैल्यू का उपयोग किया जाना चाहिए

int (*f3)(void*) = Lambda::ptr<int>(b);
printf("%d\n", f3(nullptr)); // 103

और मामले में डेटा का उपयोग किया जाता है

auto b2 = [&](void* data) {return *(int*)(data) + a;};
int (*f4)(void*) = Lambda::ptr<int>(b2);
int data = 5;
printf("%d\n", f4(&data)); // 108

3
यह निश्चित रूप से सबसे सुविधाजनक समाधान है जिसे मैंने लैम्ब्डा को सी-स्टाइल फ़ंक्शन पॉइंटर में बदलने के लिए देखा है। इसे एक तर्क के रूप में लेने वाले फ़ंक्शन को बस अपने राज्य का प्रतिनिधित्व करने वाले एक अतिरिक्त पैरामीटर की आवश्यकता होगी, जिसे अक्सर सी पुस्तकालयों में "शून्य * उपयोगकर्ता" नाम दिया जाता है, ताकि इसे कॉल करते समय इसे फ़ंक्शन पॉइंटर में पास किया जा सके।
कोडोस्कोप

10

स्थानीय रूप से वैश्विक (स्थिर) विधि का उपयोग करके इसका पालन किया जा सकता है

template <class F>
auto cify_no_args(F&& f) {
  static F fn = std::forward<F>(f);
  return [] {
    return fn();
  };
}

मान लीजिए हमारे पास है

void some_c_func(void (*callback)());

तो उपयोग होगा

some_c_func(cify_no_args([&] {
  // code
}));

यह काम करता है क्योंकि प्रत्येक लैम्ब्डा में एक अद्वितीय हस्ताक्षर होता है, इसलिए इसे स्थिर बनाना कोई समस्या नहीं है। इसके बाद एक जेनेरिक आवरण होता है जिसमें विभिन्न प्रकार के तर्क होते हैं और उसी पद्धति का उपयोग करके कोई भी रिटर्न प्रकार होता है।

template <class F>
struct lambda_traits : lambda_traits<decltype(&F::operator())>
{ };

template <typename F, typename R, typename... Args>
struct lambda_traits<R(F::*)(Args...)> : lambda_traits<R(F::*)(Args...) const>
{ };

template <class F, class R, class... Args>
struct lambda_traits<R(F::*)(Args...) const> {
    using pointer = typename std::add_pointer<R(Args...)>::type;

    static pointer cify(F&& f) {
        static F fn = std::forward<F>(f);
        return [](Args... args) {
            return fn(std::forward<Args>(args)...);
        };
    }
};

template <class F>
inline lambda_traits<F>::pointer cify(F&& f) {
    return lambda_traits<F>::cify(std::forward<F>(f));
}

और इसी तरह का उपयोग

void some_c_func(int (*callback)(some_struct*, float));

some_c_func(cify([&](some_struct* s, float f) {
    // making use of "s" and "f"
    return 0;
}));

1
ध्यान रखें कि यह क्लोजर (जब ptr हो रहा है) + args (कॉल करते समय) की नकल करेगा। अन्यथा, यह एक सुरुचिपूर्ण समाधान है
इवान सन्ज़-कारासा

हेडर-ओनली हेल्पर लाइब्रेरी: gist.github.com/isc30/fab67e5956fe8f2097bed84ebc42c1e8
Ivan Sanz-Carasa

1
@ IvanSanz-Carasa इंगित करने के लिए धन्यवाद। बंद प्रकार CopyAssignable नहीं हैं, लेकिन फंक्शनलर्स हैं। तो आप सही हैं, यहां पर सही अग्रेषण का उपयोग करना बेहतर है। दूसरी ओर आर्ग्स के लिए हम बहुत कुछ नहीं कर सकते क्योंकि सादे सी सार्वभौमिक संदर्भों का समर्थन नहीं करते हैं, लेकिन कम से कम हम अपने लैम्बडा पर वापस मूल्यों को अग्रेषित कर सकते हैं। यह एक अतिरिक्त प्रतिलिपि सहेज सकता है। कोड संपादित किया है।
व्लादिमीर टैलिबिन

@RiaD हाँ, क्योंकि लंबोदर एक स्थिर उदाहरण है जिसके लिए आपको संदर्भ के बजाय कैप्चर करने की आवश्यकता होगी, जैसे कि आपके फॉर-लूप में =उपयोग के बजाय &i
व्लादिमीर टैलिबिन

5

हेहे - काफी पुराना सवाल है, लेकिन फिर भी ...

#include <iostream>
#include <vector>
#include <functional>

using namespace std;

// We dont try to outsmart the compiler...
template<typename T>
int ftw(const char *fpath, T callback) {
  return callback(fpath);
}

int main()
{
  vector<string> entries;

  // ... now the @ftw can accept lambda
  int ret = ftw("/etc", [&](const char *fpath) -> int {
    entries.push_back(fpath);
    return 0;
  });

  // ... and function object too 
  struct _ {
    static int lambda(vector<string>& entries, const char* fpath) {
      entries.push_back(fpath);
      return 0;
    }
  };
  ret = ftw("/tmp", bind(_::lambda, ref(entries), placeholders::_1));

  for (auto entry : entries ) {
    cout << entry << endl;
  }

  return ret;
}

0

कैप्चरिंग लैम्ब्डा को फंक्शन पॉइंटर में बदलने का हैकिश तरीका है, लेकिन इसका इस्तेमाल करते समय आपको सावधान रहने की जरूरत है:

/codereview/79612/c-ifying-a-capturing-lambda

आपका कोड तब इस तरह दिखेगा (चेतावनी: मस्तिष्क संकलन):

int main()
{

    vector<string> entries;

    auto const callback = cify<int(*)(const char *, const struct stat*,
        int)>([&](const char *fpath, const struct stat *sb,
        int typeflag) -> int {
        entries.push_back(fpath);
        return 0;
    });

    int ret = ftw("/etc", callback, 1);

    for (auto entry : entries ) {
        cout << entry << endl;
    }

    return ret;
}

0

मेरा समाधान, बस एक स्थिर लैंबडा को संदर्भित करने के लिए एक फ़ंक्शन पॉइंटर का उपयोग करें।

typedef int (* MYPROC)(int);

void fun(MYPROC m)
{
    cout << m(100) << endl;
}

template<class T>
void fun2(T f)
{
    cout << f(100) << endl;
}

void useLambdaAsFunPtr()
{
    int p = 7;
    auto f = [p](int a)->int {return a * p; };

    //fun(f);//error
    fun2(f);
}

void useLambdaAsFunPtr2()
{
    int p = 7;
    static auto f = [p](int a)->int {return a * p; };
    MYPROC ff = [](int i)->int { return f(i); };
    //here, it works!
    fun(ff);
}

void test()
{
    useLambdaAsFunPtr2();
}

-1

यहाँ एक उत्तर मिला: http://meh.schizofreni.co/programming/magic/2013/01/23/function-pointer-from-lambda.html

यह धर्मान्तरित lambda pointerकरने के लिए void*और परिवर्तित वापस जब जरूरत।

  1. को void*:

    auto voidfunction = नया घोषणापत्र (to_function (लैम्ब्डा)) (to_function (लैम्ब्डा));

  2. से void*:

    ऑटो फ़ंक्शन = static_cast <std :: function *> (voidfunction);

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