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


94

我在玩C ++ lambda及其隐式转换为函数指针。我的开始示例是将它们用作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将条目追加到某个对象?


您正在使用什么编译器?是VS10吗?
Ramon Zarazua B.

gcc版本4.6.1 20110801 [gcc-4_6-branch修订版177033](SUSE Linux)
duncan

4
通常,将状态传递给回调的C方法是通过回调的额外参数(通常为类型void *)来完成的。如果您正在使用的库允许使用此额外参数,则将找到一种解决方法。否则,您将无法完全实现您想要的工作。
Alexandre C.

是。我意识到ftw.h和nftw.h的api有缺陷。我会尝试fts.h
duncan

1
大!/usr/include/fts.h:41:3:错误:#error“ <fts.h>无法与-D_FILE_OFFSET_BITS == 64”一起使用
duncan

Answers:


47

由于捕获lambda需要保留状态,因此实际上并没有简单的“解决方法”,因为它们不是只是普通的功能。关于函数指针的要点是它指向单个全局函数,并且此信息没有状态的余地。

最接近的解决方法(本质上是放弃有状态性)是提供某种类型的全局变量,可从您的lambda /函数访问该变量。例如,您可以制作一个传统的仿函数对象,并为其赋予一个静态成员函数,该成员函数引用某些唯一的(全局/静态)实例。

但这有点违反了捕获lambda的全部目的。


3
较干净的解决方案是将lambda包装在适配器内,假设函数指针具有上下文参数。
Raymond Chen

4
@RaymondChen:好吧,如果您可以自由定义函数的使用方式,那么可以的。尽管在这种情况下,将参数作为lambda本身的参数甚至会更加容易!
Kerrek SB 2011年

3
@KerrekSB将全局变量放在a namespace并将其标记为thread_local,这就是ftw我选择用于解决类似问题的方法。
KjellHedström2014年

“函数指针指向单个全局函数,并且该信息没有状态的余地。” ->像Java这样的语言怎么能做到呢?好吧,当然,因为该单个全局函数是在运行时创建的并且状态(或对它的引用嵌入其自己的代码中。这整点-应该不会是一个单一的,全球性的功能,但多个全局函数-每个时间拉姆达在运行系统中使用。在C ++中真的没有这样做吗?(我认为std :: function正是为该目的而设计的)
德克斯特(Dexter

1
@Dexter:errr ..简短答案为否,长答案涉及运算符重载。无论如何,我的观点是正确的。Java是与C ++不同的另一种语言。Java没有指针(或可重载的调用运算符),并且比较无法正常工作。
Kerrek SB

47

我只是遇到了这个问题。

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

编辑:当我遇到无法修改原始函数签名但无法使用lambda的旧代码时,我不得不重新审视它。下面的解决方案不需要修改原始函数的函数签名:

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

72
不,这不是公认的答案。关键不是ftwstd::function代替函数指针...
Gregory Pakosz 2013年

此答案中提出的第二个解决方案通过保留原始签名解决了@ gregory-pakosz的担忧,但由于引入了全局状态,因此效果仍然不佳。如果ftw有一个void * userdata参数,那么我希望使用@ evgeny-karpov的答案。
prideout

@prideout同意-我也不喜欢全球状态。不幸的是,假设ftw的签名无法修改,并且假定它没有void *用户数据,则状态必须存储在某个地方。我在使用第3方库时遇到了这个问题。只要库不捕获回调并在以后使用它,此方法就可以很好地工作,在这种情况下,全局变量就像调用堆栈上的一个额外参数一样。如果可以修改ftw的签名,那么我宁愿使用std :: function而不是void * userdata。
杰伊·韦斯特

1
这是一个极其复杂和有用的解决方案,@ Gregory我应该告诉您“它有效”。
佛罗伦萨

16

原版的

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)约定。默认情况下,使用此约定,并且应使用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 

也可以这样使用

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

3
这绝对是我看到的将lambda转换为C样式函数指针的最方便的解决方案。将其作为参数的函数仅需要一个表示其状态的附加参数,在C库中通常将其命名为“ void * user”,以便在调用它时可以将其传递给函数指针。
Codoscope

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

之所以可行,是因为每个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 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;
}));

1
请注意,这将复制闭包(获取ptr时)+ args(调用时)。否则,这是一个很好的解决方案
Ivan Sanz-Carasa


1
@ IvanSanz-Carasa感谢您指出。闭包类型不是CopyAssignable,但是函子是。所以你说对了,最好在这里使用完美的转发。另一方面,对于args,我们不能做太多事情,因为普通C不支持通用引用,但是至少我们可以将值转发回lambda。这样可以节省额外的副本。已编辑代码。
弗拉基米尔·塔利宾(Fladimir Talybin),

@RiaD是的,因为这里的λ静态实例,您将需要捕获的参考,而不是,例如,而不是=&i在你的for循环。
弗拉基米尔·塔利宾(Fladimir Talybin),

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

有一种将捕获的lambda转换为函数指针的方法,但是在使用它时需要小心:

/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

我的解决方案,只需使用函数指针来引用静态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();
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.