2

I have a piece of code which calls an async rdma-write. The rdma API receives a void* context which I would like to use to pass a callback to be called when the operation finishes.

void invoke_async_operation(... some stuff to capture ...) {
  ...
  MyCallBackType* my_callback = // Create callback somehow
  rdma_post_write(..., my_callback);
  ...
}

void on_complete(void* context) {
  (*(MyCallbackType*)context)();
}

I thought using a lambda here would be best, because it will easily capture all the context which is required to the later callback invokement. However I saw in What is the lifetime of a C++ lambda expression? that a lambda lifetime is limited to the scope where it was defined.

Note that I can't copy the lambda, because context is a pointer.

What is the correct approach here? Should I insist on using lambdas and prolong their lifetime somehow, or is there a better way? Thanks.

Christophe
  • 68,716
  • 7
  • 72
  • 138
Elad Weiss
  • 3,662
  • 3
  • 22
  • 50

1 Answers1

2

Lifetime of a lambda

The object that represents the lamda expression and allows to invoke it, obeys indeed the usual scoping rules.

However this object can be copied (e.g passing it as argument to a function or a constructor, or assigning it to a global, or whatever else you want to do) so that the lambda can be invoked at any later point, even after the scope it was initially defined in is left.

Because of exactly this potentially long survival of lambdas, you can find quite a few questions, blogs or books that will advise on careful use of the lambda capture, especially if captured by reference, because the lambda itself (and not its anonymous proxy object) can be called even after the referred objects are destroyed.

Your callback issue

You are constraint in your design by the use of an OS callback that can only convey a raw pointer that was passed to it when the callback was set up.

The way to approach this could be to use a std::function object of the standard <functional> library. Here a small function to show you how it works:

function<void()>* preparatory_work() {
    auto l = [](){ cout<< "My lambda is fine !" <<endl; } ;  // lambda 
    function<void ()> f = l;                                 // functor
    auto p = new function<void()>(l);                        // a functor on the heap
    
    l();  // inovke the lambda object 
    f();  // invoke the functor 
    (*p)(); // invoike functor via a pointer 

    return p; 
}

Function objects are as handy to use as any other object and as easy to declare as function pointers. They are however much more powerful than function pointers, because they can refer basically to any callable object.

As you see, in the example above, I allocated a function objet with new, and returned its pointer. So you could indeed later invoke this function:

int main() {
    auto fcp = preparatory_work();  // fcp is a pointer 
    (*fcp)();
                                    // and even with casting as you would like
    void *x = (void*)fcp; 
    (*(function<void()>*)x)();   // YES !!! 
} 

Here an online demo

Community
  • 1
  • 1
Christophe
  • 68,716
  • 7
  • 72
  • 138
  • I think copying is not an option (edited the question). So I guess the global option remains. That means map, since I can have many. – Elad Weiss Oct 29 '17 at 09:44
  • 1
    @EladWeiss with your edit I understand better your question. Look at the solution in my edit. – Christophe Oct 29 '17 at 10:32