c++ - تمرير التقاط lambda كمؤشر وظيفة




c++11 function-pointers (5)

أعرف أن هذا قديم بعض الشيء ..

لكنني أردت أن أضيف:

يمكن التعامل مع تعبير Lambda (حتى تلك التي تم التقاطها) كمؤشر funtion (مؤشر إلى وظيفة عضو).

إنه أمر صعب لأن تعبير Lambda ليس وظيفة بسيطة. هو في الواقع كائن مع عامل التشغيل ().

عندما تكون مبدعًا ، يمكنك استخدام هذا! التفكير في فئة "وظيفة" في نمط الأمراض المنقولة جنسيا :: وظيفة. إذا قمت بحفظ الكائن!

يمكنك أيضًا استخدام مؤشر الوظيفة.

لاستخدام مؤشر الوظيفة ، يمكنك استخدام ما يلي:

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 نأمل أن يساعد:)

تحية!

تحرير: إزالة الإبر قالب FP ، وإزالة المعلمة مؤشر وظيفة ، إعادة تسمية ل lambda_expression

تحديث 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;
}

هل من الممكن تمرير دالة lambda كمؤشر دالة؟ إذا كان الأمر كذلك ، يجب أن أفعل شيئًا غير صحيح لأنني أتلقى خطأ في الترجمة.

النظر في المثال التالي

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 منه هو أنه لا يمكن التعامل مع لامدا باعتباره constexpr لذلك لذلك لا يمكنني تمريره كمؤشر دالة؟ لقد حاولت إجراء x const أيضًا ، لكن هذا لا يبدو مفيدًا.


اختصار لاستخدام lambda كمؤشر دالة C هو:

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

استخدام Curl على سبيل المثال ( curl debug info )

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);

توضح إجابة شفيق يغمور بشكل صحيح لماذا لا يمكن تمرير لامدا كمؤشر وظيفة إذا كان لديه التقاط. أود إظهار حلين بسيطين للمشكلة.

  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; }
      };
    }

في حين أن نهج القالب ذكي لأسباب مختلفة ، من المهم أن نتذكر دورة حياة لامدا والمتغيرات التي تم التقاطها. إذا كان سيتم استخدام أي شكل من أشكال مؤشر lambda ولم يكن lambda استمرارًا نزوليًا ، فيجب استخدام نسخة [=] lambda فقط. أي أنه حتى ذلك الحين ، فإن التقاط مؤشر إلى متغير على المكدس يكون غير آمن إذا كان عمر تلك المؤشرات التي تم التقاطها (فك المكدس) أقصر من عمر lambda.

حل أبسط لالتقاط lambda كمؤشر هو:

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

على سبيل المثال ، new std::function<void()>([=]() -> void {...}

فقط تذكر أن delete pLamdba فيما بعد delete pLamdba لذا تأكد من عدم تسريب ذاكرة lambda. سر إدراك هنا هو أن lambdas يمكنها التقاط lambdas (اسأل نفسك كيف يعمل ذلك) وأيضًا لكي تعمل std::function بشكل عام ، يجب أن يحتوي تطبيق lambda على معلومات داخلية كافية لتوفير إمكانية الوصول إلى حجم lambda (و الملتقطة) البيانات (وهذا هو السبب يجب أن تعمل delete [تشغيل المدمرات من أنواع الملتقطة]).


لا يمكن تحويل لامدا إلا إلى مؤشر دالة إذا لم يتم التقاطها ، من القسم 2.1.2 من مشروع C ++ 11 القياسي [expr.prim.lambda] يقول ( منجم التركيز ):

يحتوي نوع الإغلاق الخاص بتعبير lambda بدون التقاط lambda على وظيفة تحويل const غير صريحة عامة غير افتراضية لمؤشر يعمل بنفس المعلمة وأنواع الإرجاع مثل مشغل استدعاء دالة نوع الإغلاق. يجب أن تكون القيمة التي يتم إرجاعها بواسطة وظيفة التحويل هذه هي عنوان دالة ، عند الاحتجاج بها ، يكون لها نفس تأثير استدعاء عامل استدعاء دالة نوع الإغلاق.

ملاحظة ، يغطي cppreference هذا أيضًا في القسم الخاص بوظائف Lambda .

لذلك ستعمل البدائل التالية:

typedef bool(*DecisionFn)(int);

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

وكذلك هذا:

typedef bool(*DecisionFn)();

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

وكما يشير 5gon12eder ، يمكنك أيضًا استخدام std::function ، لكن لاحظ أن std::function ثقيلة الوزن ، لذا فهي ليست مقايضة أقل تكلفة.





function-pointers