C++ lambda with captures as a function pointer

后端 未结 8 794
离开以前
离开以前 2020-11-22 15:35

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 expecte

相关标签:
8条回答
  • 2020-11-22 15:44

    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.

    0 讨论(0)
  • 2020-11-22 15:44

    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();
    }
    
    0 讨论(0)
  • 2020-11-22 15:45

    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
    
    0 讨论(0)
  • 2020-11-22 15:46

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

    0 讨论(0)
  • 2020-11-22 15:53

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 15:56

    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;
    }
    
    0 讨论(0)
提交回复
热议问题