ফাংশন পয়েন্টার হিসাবে ক্যাপচার সহ সি ++ ল্যাম্বদা


94

আমি সি ++ ল্যাম্বডাস এবং ফাংশন পয়েন্টারে তাদের অন্তর্নিহিত রূপান্তর নিয়ে খেলছিলাম। আমার সূচনা উদাহরণটি এগুলি ftw ফাংশনের কলব্যাক হিসাবে ব্যবহার করছিল। এটি প্রত্যাশার মতো কাজ করে।

#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?
রামন জারাজুয়া বি।

জিসিসি সংস্করণ 4.6.1 20110801 [জিসিসি-4_6-শাখা সংস্করণ 177033] (SUSE লিনাক্স)
ডানকান

4
সাধারণত, কলব্যাকগুলিতে স্টেট পাস করার সি উপায়টি কলব্যাকের (সাধারণত টাইপের ধরণের void *) অতিরিক্ত যুক্তির মাধ্যমে করা হয় । আপনি যে লাইব্রেরিটি ব্যবহার করছেন সেটি যদি এই অতিরিক্ত যুক্তির অনুমতি দেয় তবে আপনি একটি কার্যবিধির সন্ধান পাবেন। অন্যথায়, আপনি যা করতে চান তা পরিষ্কারভাবে অর্জন করার কোনও উপায় নেই।
আলেকজান্দ্রি সি

হ্যাঁ. আমি বুঝতে পারি ftw.h এবং nftw.h এর এপিআই ত্রুটিযুক্ত। আমি fts.h চেষ্টা করবে
ডানকান

4
দুর্দান্ত! /usr/include/fts.h:41:3: ত্রুটি: #error "<fts.h> -D_FILE_OFFSET_BITS সঙ্গে == 64 ব্যবহার করা যাবে না"
ডানকান

উত্তর:


48

যেহেতু ক্যাপচার lambdas একটি রাষ্ট্র সংরক্ষণ করতে হবে, সেখানে নেই সত্যিই একটি সহজ "কার্যসংক্রান্ত", যেহেতু তারা হয় না শুধু সাধারণ ফাংশন। একটি ফাংশন পয়েন্টার সম্পর্কে বিন্দুটি হ'ল এটি একক, বৈশ্বিক ফাংশনকে নির্দেশ করে এবং এই তথ্যের কোনও রাজ্যের কোনও স্থান নেই।

নিকটতম workaround (যে মূলত রাষ্ট্রীয়তা বাদ দেয়) হ'ল কিছু ধরণের বিশ্বব্যাপী ভেরিয়েবল সরবরাহ করা যা আপনার ল্যাম্বদা / ফাংশন থেকে অ্যাক্সেস করা হয়। উদাহরণস্বরূপ, আপনি একটি traditionalতিহ্যবাহী ফান্টর অবজেক্ট তৈরি করতে এবং এটি একটি স্থির সদস্য ফাংশন দিতে পারেন যা কিছু অনন্য (গ্লোবাল / স্ট্যাটিক) উদাহরণকে বোঝায়।

তবে এগুলি ল্যাম্বডাস বন্দী করার পুরো উদ্দেশ্যকে পরাস্ত করা।


4
একটি ক্লিনার সলিউশন হ'ল ল্যাম্বডাকে অ্যাডাপ্টারের অভ্যন্তরে আবদ্ধ করা, ধরে নেওয়া যে ফাংশন পয়েন্টারের একটি প্রসঙ্গ পরামিতি রয়েছে।
রেমন্ড চেন

4
@ রেমন্ডচেন: ঠিক আছে, আপনি যদি ফাংশনটি কীভাবে ব্যবহার করতে হয় তা নির্ধারণ করতে স্বাধীনভাবে থাকেন তবে হ্যাঁ, এটি একটি বিকল্প। যদিও সেক্ষেত্রে কেবল পরামিতিটিকে ল্যাম্বদার একটি আর্গুমেন্ট হিসাবে তৈরি করা আরও সহজ হবে!
কেরেরেক এসবি

4
@ কেরেকএসবি গ্লোবাল ভেরিয়েবলগুলিকে একটি করে রাখে namespaceএবং সেগুলিকে চিহ্নিত করে thread_local, এটিই এ জাতীয় ftwকিছু সমাধান করার জন্য আমি যে পদ্ধতিটি বেছে নিয়েছি।
কেজেল হেডস্ট্রোম

"একটি ফাংশন পয়েন্টার একক, বৈশ্বিক ফাংশনকে নির্দেশ করে এবং এই তথ্যের কোনও রাজ্যের কোনও স্থান নেই।" -> জাভা এর মতো ভাষা কীভাবে তখন এটি সম্পাদন করতে পারে? ওয়েল, অবশ্যই, কারণ যে একক, গ্লোবাল ফাংশন তৈরি করা হয় রানটাইম এ এবং এম্বেড রাজ্য (বরং বা রেফারেন্স নিজস্ব কোডে এটি)। যে হয় পুরো পয়েন্ট - সেখানে উচিত না একটি হতে একক, গ্লোবাল ফাংশন কিন্তু একাধিক বিশ্বব্যাপী ফাংশন - প্রতিটি সময় ল্যামডা জন্য এক রানটাইম ব্যবহার করা হয়। সি ++ তে আসলে কিছুই নেই যা তা করে? (আমি ভেবেছিলাম std :: ফাংশনটি সেই একক উদ্দেশ্যে হুবহু তৈরি করা হয়েছে)
ডেক্সটার

4
@ ডেক্সটার: ভুল! সংক্ষিপ্ত উত্তর হ'ল না, দীর্ঘ উত্তরটিতে অপারেটর ওভারলোডিং জড়িত। নির্বিশেষে, আমার বক্তব্য দাঁড়িয়েছে। জাভা একটি আলাদা ভাষা যা সি ++ এর মতো নয়; জাভাতে পয়েন্টার নেই (বা ওভারলোডযোগ্য কল অপারেটর) এবং তুলনাটি ভাল কাজ করে না।
কেরেক এসবি

47

আমি শুধু এই সমস্যা মধ্যে দৌড়ে।

কোডটি ল্যাম্বদা ক্যাপচার ছাড়াই সূক্ষ্ম সংকলন করে, তবে ল্যাম্বদা ক্যাপচারের সাথে একটি ধরণের রূপান্তর ত্রুটি রয়েছে।

সি ++ 11 এর সাথে সমাধানটি হ'ল std::function(সম্পাদনা করুন: আরও একটি সমাধান যা ফাংশন স্বাক্ষর সংশোধন করার প্রয়োজন হয় না এই উদাহরণের পরে দেখানো হয়েছে)। আপনি ব্যবহার করতে পারেন boost::function(যা আসলে উল্লেখযোগ্যভাবে দ্রুত চলে) runs উদাহরণ কোড - পরিবর্তিত হয়েছে যাতে এটি সংকলিত হয়, এর সাথে সংকলিত 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;
}

73
না এটি গ্রহণযোগ্য উত্তর হওয়া উচিত নয়। বিন্দু পরিবর্তন করা হয় না ftwনিতে std::functionএকটি ফাংশন পয়েন্টার পরিবর্তে ...
গ্রেগরী Pakosz

এই উত্তরের প্রস্তাবিত দ্বিতীয় সমাধানটি মূল স্বাক্ষর সংরক্ষণ করে @ গ্রেগরি-পাকোস্জ থেকে উদ্বেগকে সম্বোধন করেছে, তবে এটি এখনও দুর্দান্ত নয় কারণ এটি বিশ্বব্যাপী রাষ্ট্রের পরিচয় দেয়। যদি ftwকোনও বাতিল * ইউজারডাটা যুক্তি থাকে তবে আমি @ ইজজেনি-করপভ থেকে উত্তরটি পছন্দ করব।
গর্বিত

@ প্রীডআউট সম্মত - আমি বিশ্বব্যাপী রাষ্ট্রও পছন্দ করি না। দুর্ভাগ্যক্রমে, ধরে নেওয়া ftw এর স্বাক্ষরটি সংশোধন করা যাবে না এবং এটি শূন্য * ব্যবহারডাটা না থাকলে, রাষ্ট্রটি কোথাও সংরক্ষণ করতে হবে। আমি একটি তৃতীয় পক্ষের লাইব্রেরি ব্যবহার করে এই সমস্যায় পড়েছি। এটি দীর্ঘক্ষণ কাজ করবে যতক্ষণ না গ্রন্থাগার কলব্যাক ক্যাপচার করে না এবং এটি পরে ব্যবহার না করে, সেক্ষেত্রে গ্লোবাল ভেরিয়েবল কেবল কল স্ট্যাকের অতিরিক্ত প্যারামিটারের মতো কাজ করে। যদি ftw এর স্বাক্ষরটি সংশোধন করা যায় তবে আমি শূন্য * ইউজারডাটার পরিবর্তে std :: ফাংশনটি ব্যবহার করতে পছন্দ করব।
জে ওয়েস্ট

4
এটি একটি অত্যন্ত জটিল এবং দরকারী সমাধান, @ গ্রেগরির আমি আপনাকে "এটি কাজ করে" বলতে হবে।
23:58

17

মূল

ল্যাম্বদা ফাংশনগুলি খুব সুবিধাজনক এবং একটি কোড হ্রাস করে। আমার ক্ষেত্রে সমান্তরাল প্রোগ্রামিংয়ের জন্য আমার ল্যাম্বডাস দরকার ছিল। তবে এটির জন্য ক্যাপচারিং এবং ফাংশন পয়েন্টার প্রয়োজন। আমার সমাধান এখানে। তবে আপনি যে ভেরিয়েবলের ক্যাপচার করেছেন তার সুযোগ সম্পর্কে সতর্ক থাকুন।

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

হালনাগাদ

উন্নত সংস্করণ

ফাংশন পয়েন্টার হিসাবে ক্যাপচারের সাথে সি ++ ল্যাম্বদা সম্পর্কে প্রথম পোস্টটি বেশ কিছুক্ষণ পরে গেছে। যেহেতু এটি আমার এবং অন্যান্য ব্যক্তির পক্ষে ব্যবহারযোগ্য ছিল আমি কিছুটা উন্নতি করেছি।

স্ট্যান্ডার্ড ফাংশন সি পয়েন্টার এপিআই অকার্যকর fn (শূন্য * ডেটা) কনভেনশন ব্যবহার করে। ডিফল্টরূপে এই কনভেনশনটি ব্যবহৃত হয় এবং ল্যাম্বডাকে শূন্য * আর্গুমেন্ট দিয়ে ঘোষণা করা উচিত।

উন্নত বাস্তবায়ন

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

এক্সপামল

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

ল্যাম্বদা ক্যাপচারের সাথে সি পয়েন্টারে রূপান্তর করা হচ্ছে

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

4
এটি ল্যাম্বডাকে সি-স্টাইলের ফাংশন পয়েন্টারে রূপান্তর করতে অবশ্যই দেখা সবচেয়ে সুবিধাজনক সমাধান। এটি আর্গুমেন্ট হিসাবে গ্রহণ করে ফাংশনটির কেবলমাত্র তার রাজ্যের প্রতিনিধিত্বকারী একটি অতিরিক্ত প্যারামিটারের প্রয়োজন হবে, প্রায়শই সি লাইব্রেরিতে "অকার্যকর * ব্যবহারকারী" নামকরণ করা হয়, যাতে এটি কল করার সময় এটি ফাংশন পয়েন্টারে পাস করতে পারে।
কোডকোস্কোপ

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

এটি কাজ করে কারণ প্রতিটি ল্যাম্বডায় একটি স্বতন্ত্র স্বাক্ষর রয়েছে তাই এটি স্থির করে তোলা কোনও সমস্যা নয়। নিম্নলিখিতটি জেনেরিক মোড়কে নীচে বর্ণিত সংখ্যার আর্গুমেন্ট এবং একই পদ্ধতি ব্যবহার করে যে কোনও রিটার্ন টাইপ রয়েছে is

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

4
সচেতন হন যে এটি ক্লোজারটি (পিটিআর পাওয়ার সময়) + আরগগুলি (কল করার সময়) অনুলিপি করবে। অন্যথায়, এটি একটি মার্জিত সমাধান
ইভান সানজ-কারাসা

কেবলমাত্র শিরোনাম সহায়ক লাইব্রেরি: gist.github.com/isc30/fab67e5956fe8f2097bed84ebc42c1e8
ইভান

4
@ ইভানসানজ-কারাসা নির্দেশ করার জন্য ধন্যবাদ। বন্ধের প্রকারগুলি অনুলিপিযোগ্য নয়, তবে ফান্টেক্টরগুলি। সুতরাং আপনি ঠিক, এখানে নিখুঁত ফরোয়ার্ডিং ব্যবহার করা ভাল। অন্যদিকে আর্গুমেন্টগুলির জন্য আমরা অনেক কিছুই করতে পারি না কারণ সরল সি সর্বজনীন রেফারেন্সকে সমর্থন করে না, তবে কমপক্ষে আমরা মানগুলি আমাদের ল্যাম্বডায় ফরোয়ার্ড করতে পারি। এটি একটি অতিরিক্ত অনুলিপি সংরক্ষণ করতে পারে। সম্পাদিত কোড রয়েছে।
ভ্লাদিমির তাল্যবিন

@ রিয়াড হ্যাঁ, কারণ ল্যাম্বদা এখানে একটি স্থির উদাহরণ হিসাবে আপনার পরিবর্তে লুপটি =ব্যবহারের পরিবর্তে রেফারেন্সের মাধ্যমে ক্যাপচার করতে হবে &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 = new decltype (to_function (lambda)) (to_function (lambda));

  2. থেকে void* :

    অটো ফাংশন = স্ট্যাটিক_কাস্ট <স্টাড :: ফাংশন *> (শূন্যতা);

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.