捕获作为函数指针的C++ lambda

but5z9lq  于 2023-01-10  发布在  其他
关注(0)|答案(9)|浏览(225)

我在玩C++ lambdas和它们到函数指针的隐式转换。我的开始例子是用它们作为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)’

经过一些阅读,我了解到使用捕获的lambda不能隐式转换为函数指针。
有什么解决办法吗?它们不能被“隐式”转换的事实是否意味着它们可以被“显式”转换?(我尝试过强制转换,但没有成功)。有什么干净的方法可以修改工作示例,以便我可以使用lambda将条目附加到某个对象?

3zwjbxry

3zwjbxry1#

我刚碰到这个问题。
如果没有lambda捕获,代码可以很好地编译,但是使用lambda捕获时会出现类型转换错误。
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;
}

编辑:当我遇到遗留代码时,我不得不重新考虑这个问题,因为我不能修改原始函数的签名,但仍然需要使用lambdas。下面是一个不需要修改原始函数签名的解决方案:

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

lb3vh1jj2#

因为捕获lambda需要保留一个状态,所以实际上没有一个简单的“变通方法”,因为它们 * 不 * 只是普通的函数。函数指针的要点在于它指向一个单独的全局函数,而这个信息没有给状态留空间。
最接近的解决方案(本质上放弃了状态性)是提供某种类型的全局变量,它可以从lambda/函数访问。例如,可以创建一个传统的functor对象,并给予它一个静态成员函数,它引用某个唯一的(全局/静态)示例。
但这有点违背了捕捉拉姆达的全部目的。

hgc7kmma

hgc7kmma3#

    • 原件**

Lambda函数非常方便并且减少了代码。在我的例子中,我需要Lambda来进行并行编程。但是它需要捕获和函数指针。我的解决方案在这里。但是要注意你捕获的变量的范围。

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++ lambda函数指针的文章发表以来已经有一段时间了,因为它对我和其他人都有用,我做了一些改进。
标准函数C指针api使用void fn(void * data)约定。默认情况下使用此约定,并且lambda应该使用void * 参数声明。
改进执行情况

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

将带有捕获的lambda转换为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
lqfhib0f

lqfhib0f4#

使用局部全局(静态)方法,可以按如下方式完成

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

这样做是因为每个lambda都有一个唯一的签名,所以使它成为静态不是问题。下面是一个泛型 Package 器,它有可变数量的参数和使用相同方法的任何返回类型。

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

jvidinwx5#

呵呵,这是个老问题了,不过还是......

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

cqoc49vn6#

我的解决方案是,只使用函数指针来引用静态lambda。

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

zphenhs47#

有一种方法可以将捕获lambda转换为函数指针,但在使用时需要小心:
https://codereview.stackexchange.com/questions/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;
}
aemubtdh

aemubtdh8#

@vladimir-talybin制作的answer有一个小问题:

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

也就是说,如果lambda在函数中被调用了两次,那么只有第一次调用是有效的。

// only a demo
void call(std::vector<int>& nums) {
  static int i = 0;
  cify_no_args([&]() {
    nums.emplace_back(i++);
  })();
}

int main() {
  std::vector<int> nums1, nums2;
  call(nums1);
  call(nums2);

  std::cout << nums1.size() << std::endl << nums2.size() << std::endl;
}

您将显示20的输出,这意味着call函数的第二次调用正在使用第一次调用的lambda闭包。
这是因为这个解决方案使用static来存储闭包的引用,一旦引用被存储,它就不会被改变,即使是一个新的闭包。如果闭包被析构(由于超出作用域或其他原因),情况会变得更糟。
我对这个问题的解决方案是简单地将引用转换为指针,并在每次“构造”lambda时更新指针的值:

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

开销是多两次内存访问,一次用于读,一次用于写,但是确保了正确性。

zvokhttg

zvokhttg9#

在这里找到了答案:http://meh.schizofreni.co/programming/magic/2013/01/23/function-pointer-from-lambda.html
它将lambda pointer转换为void*,并在需要时转换回来。
1.至void*
自动无效函数=新decltype(目标函数(λ))(目标函数(λ));
1.从void*开始:
auto函数= static_cast〈标准::函数 *〉(无效函数);

相关问题