0

I am trying to implement a parallel runtime using argobots api.

In the main.cpp I am using a lambda function which is argument to the "kernel" function in lib.cpp. I need to convert the lambda received in lib.hpp to a function pointer and call "lib_kernel" in lib.c. I have read many answers and came to know that converting lambdas (capture by reference) to function pointer is not possible. Is there any alternative?. I also don't have any idea how deal with the template parameter T in the "kernel" function. Please help.

// main.cpp

#include "lib.hpp"

int main(int argc, char **argv) {
    int result;
    lib::kernel([&]() {
        result = fib(10); 
    }); 
    cout << "Fib(10) = " << result << "\n";
    // fib is parallel implementation of fibonacci
    // similar to passing function pointers to threads
}

// lib.hpp

namespace lib {

    void kernel(T &&lambda) {
        // T is template argument
        // Need to convert lambda to function pointer
        // ie. lib_kernel(fptr, args)
        // Here fptr and args are received from lambda
        // ie. fptr will be fib function 
        // args is value 10.
    }
}

// lib.c

typedef void (*fork_t)(void* args);

void lib_kernel(fork_t fptr, void* args) {
    fptr(args);
}
Herschelle
  • 17
  • 6

2 Answers2

1

Pass the address of the capturing lambda into the second parameter (void *). Make a second (non-capturing) lambda to call it:

#include <iostream>

void foo(void (*func)(void *), void *arg)
{
    func(arg);
}

int main()
{
    int var = 42;
    auto lambda = [&]{std::cout << var << '\n';};
    foo([](void *f){(*(decltype(lambda) *)f)();}, &lambda);
}
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
0

You can convert a lambda to a function pointer with +, i.e.:

typedef void (*func)(void* args);

void kernel(func fn, void* args){ 
    fn(args);
}

void CallKernelWithLambda() {
    func f = +[](void*) { };
    kernel(f, nullptr);
}

However, lambdas that capture cannot be converted to a function pointer -- the following fails to compile:

typedef void (*func)(void* args);

void kernel(func fn, void* args){ 
    fn(args);
}

void CallKernelWithLambda() {
    int value = 0;
    func f = +[&](void*) { ++value; };
    kernel(f, nullptr);
}

You need to put the value that you want to capture in static storage or global scope, e.g.:

typedef void (*func)(void* args);

void kernel(func fn, void* args){ 
    fn(args);
}

int value = 0;
void CallKernelWithLambda() {
    func f = +[](void*) { ++value; };
    kernel(f, nullptr);
}

Putting the call to kernel inside of a templated function doesn't make any difference, i.e.:

typedef void (*func)(void* args);

void kernel(func fn, void* args){ 
    fn(args);
}

template <typename T>
void CallKernelWithTemplateArgumentLambda(T&& lambda) {
    kernel(+lambda, nullptr);
}
int value = 0;
void CallKernelWithLambda() {
    CallKernelWithTemplateArgumentLambda([](void*) { ++value; });
}

behaves the same way as the previous snippet.

Patrick Collins
  • 10,306
  • 5
  • 30
  • 69
  • Singletons are bad. Since the library function accepts a `void *`, you should pass your parameters there. – HolyBlackCat Oct 08 '22 at 18:33
  • @HolyBlackCat yes, but my impression from the question is that OP doesn't control the kernel function, so if they want to do something like "track the number of times the lambda is invoked," it can't be done without introducing some global state. – Patrick Collins Oct 08 '22 at 18:34
  • 1
    The `void *` parameter makes it possible. See my answer... – HolyBlackCat Oct 08 '22 at 18:37
  • @HolyBlackCat I'm assuming this call to the kernel function is buried deep inside of a library that OP doesn't control, since otherwise the desire to put mutable state in the lambda makes less sense. – Patrick Collins Oct 08 '22 at 18:41