it-swarm-id.com

C ++ lambda dengan menangkap sebagai pointer fungsi

Saya sedang bermain dengan C++ lambdas dan konversi implisit mereka ke pointer fungsi. Contoh awal saya menggunakannya sebagai callback untuk fungsi ftw. Ini berfungsi seperti yang diharapkan.

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

Setelah memodifikasinya untuk menggunakan tangkapan:

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

Saya mendapat kesalahan kompilator:

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

Setelah membaca. Saya belajar bahwa lambdas menggunakan tangkapan tidak dapat secara implisit dikonversi ke fungsi pointer.

Apakah ada solusi untuk ini? Apakah fakta bahwa mereka tidak dapat "secara implisit" dikonversi berarti bahwa mereka dapat "secara eksplisit" dikonversi? (Saya mencoba casting, tanpa hasil). Apa yang akan menjadi cara bersih untuk memodifikasi contoh kerja sehingga saya bisa menambahkan entri ke beberapa objek menggunakan lambdas ?.

82
duncan

Karena menangkap lambda perlu mempertahankan keadaan, sebenarnya tidak ada "solusi" sederhana, karena mereka tidak hanya fungsi biasa. Poin tentang pointer fungsi adalah bahwa ia menunjuk ke fungsi global tunggal, dan informasi ini tidak memiliki ruang untuk keadaan.

Solusi terdekat (yang pada dasarnya membuang status) adalah untuk menyediakan beberapa jenis variabel global yang diakses dari fungsi/lambda Anda. Sebagai contoh, Anda bisa membuat objek functor tradisional dan memberinya fungsi anggota statis yang mengacu pada beberapa contoh unik (global/statis).

Tapi itu semacam mengalahkan seluruh tujuan menangkap lambda.

39
Kerrek SB

Saya baru saja mengalami masalah ini.

Kode mengkompilasi dengan baik tanpa tangkapan lambda, tetapi ada kesalahan konversi jenis dengan tangkapan lambda.

Solusi dengan C++ 11 adalah menggunakan std::function (edit: solusi lain yang tidak perlu memodifikasi tanda tangan fungsi ditampilkan setelah contoh ini). Anda juga bisa menggunakan boost::function (yang sebenarnya berjalan lebih cepat secara signifikan). Kode contoh - diubah sehingga akan dikompilasi, dikompilasi dengan 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;
}

Sunting: Saya harus meninjau kembali ini ketika saya berlari ke kode warisan di mana saya tidak dapat mengubah tanda tangan fungsi asli, tetapi masih perlu menggunakan lambdas. Solusi yang tidak memerlukan modifikasi tanda tangan fungsi dari fungsi asli adalah di bawah ini:

#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;
}
46
Jay West

ASLI

Fungsi Lambda sangat mudah dan mengurangi kode. Dalam kasus saya, saya membutuhkan lambdas untuk pemrograman paralel. Tapi itu membutuhkan penangkap dan fungsi pointer. Solusi saya ada di sini. Tapi hati-hati dengan ruang lingkup variabel yang Anda ambil.

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

Contoh

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

Contoh dengan nilai kembali

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

PEMBARUAN

Versi yang ditingkatkan

Sudah lama sejak posting pertama tentang C++ lambda dengan menangkap sebagai pointer fungsi diposting. Karena dapat digunakan untuk saya dan orang lain, saya membuat beberapa perbaikan.

Fungsi standar C pointer api menggunakan konvensi void fn (void * data). Secara default, konvensi ini digunakan dan lambda harus dideklarasikan dengan argumen * void.

Peningkatan implementasi

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

Mengkonversi lambda dengan menangkap ke pointer C

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

Dapat digunakan dengan cara ini juga

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

Dalam hal nilai pengembalian harus digunakan

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

Dan jika data digunakan

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

Menggunakan metode global global (statis) dapat dilakukan sebagai berikut

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

Misalkan kita punya

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

Jadi penggunaannya akan

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

Ini berfungsi karena setiap lambda memiliki tanda tangan yang unik sehingga membuatnya statis tidak menjadi masalah. Mengikuti pembungkus generik dengan sejumlah argumen dan jenis pengembalian apa pun menggunakan metode yang sama.

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 = 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 typename lambda_traits<F>::pointer cify(F&& f) {
    return lambda_traits<F>::cify(std::forward<F>(f));
}

Dan penggunaan serupa

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;
}));
6
Vladimir Talybin

Hehe - pertanyaan yang cukup lama, tapi masih ...

#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;
}
4
egorse

Ada cara meretas untuk mengubah lambda yang menangkap menjadi sebuah fungsi pointer, tetapi Anda harus berhati-hati saat menggunakannya:

https://codereview.stackexchange.com/questions/79612/c-ifying-a-capturing-lambda

Kode Anda kemudian akan terlihat seperti ini (peringatan: kompilasi otak):

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
user1095108

Solusi saya, cukup gunakan pointer fungsi untuk merujuk ke lambda statis.

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();
}
0
Zhang