Search code examples
c++clambdaextern-c

Using lambda for callback function as C function parameter


I was writing my own std::thread-like wrapper for pthread_* functions (for educational purposes). The constructor I came up with looked like this:

template<class Fn, class... Args>
explicit Thread(Fn&& fn, Args&&... args) {
    using Invoker = Thread_invoker<Fn, Args...>;
    void* (* start_fn)(void*) = [](void* invoker_ptr) -> void* {
        // ...
    };

    auto invoker = std::make_unique<Invoker>(/* ... */);
    const auto err = ::pthread_create(&handle_, nullptr, start_fn, invoker.get());
    // ...
    invoker.release();
}

I made some basic tests, the code works. But then it occurred to me that C++ functions can in theory have calling conventions that are different from C functions, and passing start_fn function pointer to pthread_create can be UB. This answer seems to confirm this.

Digging further, I found this question. Following the spirit of the accepted answer, I changed my code into this:

extern "C" using Thread_start_fn = void* (void*);

Thread_start_fn* start_fn = [](void* invoker_ptr) -> void* {
    // ...
};

Does this modification solve the problem and is the code legal now?


Solution

  • Does this modification solve the problem and is the code legal now?

    No, it doesn't solve the problem. The function pointer you can obtain from the lambda expression is only to a function with C++ language linkage.

    The way to obtain with certainty a pointer to a function with C language linkage is to use a free function. This is the approach you'll see employed in popular standard library implementations. For instance, in libstdc++

    extern "C"
      {
        static void*
        execute_native_thread_routine(void* __p)
        {
          thread::_State_ptr __t{ static_cast<thread::_State*>(__p) };
          __t->_M_run();
          return nullptr;
        }
        // ...
      }