duncan
duncan

Reputation: 6293

C++ lambda with captures as a function pointer

I was playing with C++ lambdas and their implicit conversion to function pointers. My starting example was using them as callback for the 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.

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

Upvotes: 130

Views: 89843

Answers (10)

Minli Xie
Minli Xie

Reputation: 11

Spirited by the above answers of > Jay West and > Evgeny Karpov, I get the following working code:

#include <iostream>
#include <functional>
template<typename TLambda, typename TStdFunction, 
         typename TRet, typename TCFunction>
struct LambdaPtrConverter {
public:
    explicit LambdaPtrConverter(TLambda&& pFun) { StdFun(&pFun); }
    explicit LambdaPtrConverter(TLambda& pFun) { StdFun(&pFun); }
protected:
    static TStdFunction StdFun(TLambda* pFun = nullptr) {
        static TStdFunction s_function;
        if (pFun) s_function = *pFun; 
        return s_function;
    }
    template<typename ... _Types>
    static TRet CFun(_Types... Args) {
        return StdFun()(std::forward<_Types>(Args)...);
    }
public: static TCFunction Ptr() { return (TCFunction)(CFun); }
};
//Following is helper function to void(void) function
typedef void (*FnVoidVoid)();
template<typename TLambda>
FnVoidVoid LambdaToVoidVoidFun(TLambda&& pFun) {
    return LambdaPtrConverter<TLambda, std::function<void()>, 
                              void, FnVoidVoid>(pFun).Ptr();
}  
int main(int argc, char* argv[]) {
    const char* str = "hello world";
    FnVoidVoid pFun = LambdaToVoidVoidFun(
        [str]()->void { std::cout << str << std::endl; });
    pFun();     
    return 0;
}

The code is work, as the type of each lambda function is unique, and the LambdaPtrConverter instruction is unique for the input lambda function. It uses static std::function to store lambda function. If you need other types of functions, you can write another helper function, such as LambdaToVoidVoidFun above. For example, here is another type:

typedef void (*FnVoidIntInt)(int, int);
template<typename TLambda>
FnVoidIntInt LambdaToVoidIntIntFun(TLambda&& pFun)
{
    return LambdaPtrConverter<TLambda, std::function<void(int, int)>,
                              void, FnVoidIntInt>(pFun).Ptr();
}

And you can use it like this:

FnVoidIntInt pFun = LambdaToVoidIntIntFun(
    [str](int a, int b)->void {
        std::cout << str << std::endl;
        std::cout << a << std::endl;
        std::cout << b << std::endl;
});

Be careful, the memory of the above static s_function will not be released, so this trick is only useful when you need to pass C style function pointer as entry points to some third-party frameworks, as these entry functions must always be loaded in memory before the program exits, so this deficiency won't be a big problem

Upvotes: 1

Evgeny Karpov
Evgeny Karpov

Reputation: 2466

ORIGINAL

Lambda functions are very convenient and reduce a code. In my case I needed lambdas for parallel programming. 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. As It was usable for me and other people I made some improvement.

Standard function C pointer api uses void fn(void* data) convention. By default this convention is used and lambda should be declared with a void* argument.

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

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

Upvotes: 32

byte
byte

Reputation: 11

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

That is, if the lambda is called twice in the function, then only the first call is valid, e.g.

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

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:

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.

Upvotes: 1

Vladimir Talybin
Vladimir Talybin

Reputation: 614

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

Upvotes: 24

Zhang
Zhang

Reputation: 3356

My solution, just use a function pointer to refer to a static 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();
}

Upvotes: 1

Jay West
Jay West

Reputation: 1170

I just ran into this problem.

The code compiles fine without lambda captures, but there is a type conversion error with lambda capture.

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). You can also use boost::function (which actually runs significantly faster). Example code - changed so that it would compile, compiled with 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. 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;
}

Upvotes: 56

egorse
egorse

Reputation: 59

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

Upvotes: 4

user1095108
user1095108

Reputation: 14623

There is a hackish way to convert a capturing lambda into a function pointer, but you need to be careful when using it:

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

Upvotes: 0

jesse
jesse

Reputation: 9

Found an answer here: 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.

  1. to void*:

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

  2. from void*:

    auto function = static_cast< std::function*>( voidfunction);

Upvotes: -2

Kerrek SB
Kerrek SB

Reputation: 477660

Since capturing lambdas need to preserve a state, there isn't really a simple "workaround", since they are not just ordinary functions. 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. 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.

Upvotes: 58

Related Questions