C + + lambda,以捕捉为函数指针

我在玩 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)’

After some reading. I learned that lambdas using captures 不能被隐式转换 to function pointers.

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?.

75187 次浏览

由于捕获 lambdas 需要保持一个状态,所以不存在简单的“解决方案”,因为它们只是 没有的普通函数。关于函数指针的要点是,它指向一个单一的全局函数,而这个信息没有状态的空间。

最接近的解决方案(基本上放弃了状态)是提供一些类型的全局变量,这些全局变量是从您的 lambda/函数访问的。例如,您可以创建一个传统函数对象,并为其提供一个静态成员函数,该函数引用某个惟一(全局/静态)实例。

But that's sort of defeating the entire purpose of capturing lambdas.

我只是碰到了这个问题。

该代码在没有 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;
}

在这里找到了答案: 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*:

    Auto function = static _ cast < std: : function * > ( 空虚功能) ;

有一种黑客式的方法可以将捕捉到的 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;
}

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

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

原创的

Lambda 函数非常方便,减少了代码。在我的例子中,我需要 lambdas 来进行并行编程。但是它需要捕获和函数指针。我的解决办法就在这里。但是要小心您捕获的变量的范围。

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

UPDATE

改良版

自从第一篇关于 C + + lambda 的文章发布以来,已经有一段时间了,这篇文章的函数指针是捕捉。因为它对我和其他人都有用,所以我做了一些改进。

标准函数 C 指针 api 使用 void fn (void * data)约定。默认情况下,使用此约定,并且应该使用 void * 参数声明 lambda。

改进的实施

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

Can be used this way as well

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

使用局部全局(静态)方法,它可以按照以下步骤完成

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 都有一个惟一的签名,所以使它成为静态的不成问题。下面是使用相同方法的具有可变参数数目和任何返回类型的泛型包装器。

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

我的解决方案就是用一个函数指针来引用一个静态 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();
}

The 回答 made by @vladimir-talybin has a little problem:

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 闭包。

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

我对这个问题的解决方案只是简单地将引用转换为指针,并在每次“构造”lambda 时更新指针的值:

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

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