简体   繁体   English

C++ lambda 作为 function 指针捕获

[英]C++ lambda with captures as a function pointer

I was playing with C++ lambdas and their implicit conversion to function pointers.我在玩 C++ lambda 及其隐式转换为 function 指针。 My starting example was using them as callback for the ftw function.我的起始示例是使用它们作为 ftw function 的回调。 This works as expected.这按预期工作。

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

After modifying it to use captures:修改它以使用捕获后:

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

I got the compiler error:我得到了编译器错误:

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

After some reading.经过一番阅读。 I learned that lambdas using captures can't be implicitly converted to function pointers.我了解到使用捕获的 lambda不能隐式转换为 function 指针。

Is there a workaround for this?有解决方法吗? Does the fact that they can't be "implicitly" converted mean s that they can "explicitly" converted?它们不能“隐式”转换的事实是否意味着它们可以“显式”转换? (I tried casting, without success). (我尝试铸造,但没有成功)。 What would be a clean way to modify the working example so that I could append the entries to some object using lambdas?.什么是修改工作示例的干净方法,以便我可以使用 lambdas append 到某些 object 的条目?

I just ran into this problem.我刚刚遇到了这个问题。

The code compiles fine without lambda captures, but there is a type conversion error with lambda capture.代码在没有 lambda 捕获的情况下编译得很好,但是 lambda 捕获存在类型转换错误。

Solution with C++11 is to use std::function (edit: another solution that doesn't require modifying the function signature is shown after this example). C++11 的解决方案是使用std::function (编辑:另一个不需要修改函数签名的解决方案显示在此示例之后)。 You can also use boost::function (which actually runs significantly faster).您还可以使用boost::function (实际上运行得更快)。 Example code - changed so that it would compile, compiled with gcc 4.7.1 :示例代码 - 更改为可以编译,使用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;
}

Edit: I had to revisit this when I ran into legacy code where I couldn't modify the original function signature, but still needed to use lambdas.编辑:当我遇到无法修改原始函数签名但仍需要使用 lambdas 的遗留代码时,我不得不重新审视这一点。 A solution that doesn't require modifying the function signature of the original function is below:不需要修改原始函数的函数签名的解决方案如下:

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

Since capturing lambdas need to preserve a state, there isn't really a simple "workaround", since they are not just ordinary functions.由于捕获 lambda 需要保留状态,因此实际上并没有简单的“解决方法”,因为它们不仅仅是普通函数。 The point about a function pointer is that it points to a single, global function, and this information has no room for a state.函数指针的关键在于它指向一个单一的全局函数,并且这个信息没有状态的空间。

The closest workaround (that essentially discards the statefulness) is to provide some type of global variable which is accessed from your lambda/function.最接近的解决方法(基本上放弃状态)是提供某种类型的全局变量,可以从您的 lambda/函数访问。 For example, you could make a traditional functor object and give it a static member function which refers to some unique (global/static) instance.例如,您可以创建一个传统的仿函数对象并给它一个静态成员函数,该函数引用一些唯一的(全局/静态)实例。

But that's sort of defeating the entire purpose of capturing lambdas.但这有点违背了捕获 lambdas 的全部目的。

ORIGINAL原来的

Lambda functions are very convenient and reduce a code. Lambda 函数非常方便并且减少了代码。 In my case I needed lambdas for parallel programming.就我而言,我需要 lambdas 来进行并行编程。 But it requires capturing and function pointers.但它需要捕获和函数指针。 My solution is here.我的解决方案在这里。 But be careful with scope of variables which you captured.但请注意您捕获的变量范围。

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

Example例子

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

Example with a return value带有返回值的示例

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

UPDATE更新

Improved version改良版

It was a while since first post about C++ lambda with captures as a function pointer was posted.自从第一篇关于将捕获作为函数指针的 C++ lambda 的帖子发布以来已经有一段时间了。 As It was usable for me and other people I made some improvement.因为它对我和其他人有用,所以我做了一些改进。

Standard function C pointer api uses void fn(void* data) convention.标准函数 C 指针 api 使用 void fn(void* data) 约定。 By default this convention is used and lambda should be declared with a void* argument.默认情况下使用此约定,并且应使用 void* 参数声明 lambda。

Improved implementation改进的实施

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

Converting lambda with captures to a C pointer将带有捕获的 lambda 转换为 C 指针

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

Can be used this way as well也可以这样使用

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

In case return value should be used如果应该使用返回值

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

And in case data is used如果使用数据

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

Using locally global (static) method it can be done as followed使用局部全局(静态)方法可以如下完成

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

Suppose we have假设我们有

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

So the usage will be所以用法将是

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

This works because each lambda has an unique signature so making it static is not a problem.这是可行的,因为每个 lambda 都有一个唯一的签名,因此使其成为静态不是问题。 Following is a generic wrapper with variadic number of arguments and any return type using the same method.以下是一个通用包装器,具有可变数量的参数和使用相同方法的任何返回类型。

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

And similar usage和类似的用法

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

Hehe - quite an old question, but still...呵呵 - 一个很老的问题,但仍然......

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

My solution, just use a function pointer to refer to a static lambda.我的解决方案,只需使用函数指针来引用静态 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();
}

There is a hackish way to convert a capturing lambda into a function pointer, but you need to be careful when using it:有一种 hackish 方法可以将捕获的 lambda 转换为函数指针,但是在使用它时需要小心:

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

Your code would then look like this (warning: brain compile):然后您的代码将如下所示(警告:大脑编译):

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

The answer made by @vladimir-talybin has a little problem: @vladimir-talybin 的回答有点问题:

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

That is, if the lambda is called twice in the function, then only the first call is valid, eg也就是说,如果 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;
}

You will show the output of 2 and 0 , which means that the second call of call function is using the first call's lambda closure.您将显示20的输出,这意味着call函数的第二次调用正在使用第一次调用的 lambda 闭包。

That's because the solution is using the static to store the closure's reference, and once the reference is stored, it won't be changed, even for a new closure.那是因为解决方案是使用静态来存储闭包的引用,并且一旦存储了引用,它就不会改变,即使是新的闭包。 Things get worse if the closure will get destructed (due to out of scope or else).如果闭包被破坏(由于超出范围或其他原因),情况会变得更糟。

My solution of this problem is simply turning the reference into pointer, and update the pointer's value every time we "construct" the lambda:我对这个问题的解决方案是简单地将引用转换为指针,并在每次我们“构造”lambda 时更新指针的值:

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

The overhead is two more memory access, one for read and one for write, but ensures the correctness.开销是另外两次内存访问,一次用于读取,一次用于写入,但确保了正确性。

Found an answer here: http://meh.schizofreni.co/programming/magic/2013/01/23/function-pointer-from-lambda.html在这里找到了答案:http: //meh.schizofreni.co/programming/magic/2013/01/23/function-pointer-from-lambda.html

It converts lambda pointer to void* and convert back when needed.它将lambda pointer转换为void*并在需要时转换回来。

  1. to void* : void*

    auto voidfunction = new decltype(to_function(lambda))(to_function(lambda)); auto voidfunction = new decltype(to_function(lambda))(to_function(lambda));

  2. from void* :来自void*

    auto function = static_cast< std::function*>( voidfunction);自动函数 = static_cast<std::function*>(voidfunction);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM