0

I'm wrapping a C library and want to allow using the parameter list of the callback's function pointer to determine how to cast the void* on the other side of the asynchronous call. The problem is I can't figure out how to get access to the templatized callback from within the lambda trampoline. Below is example code with a comment where the compiler error is occurring, but the invalid code should give the idea of what I'm going for.

// Hypothetical asynchronous C library call
typedef void (*future_cb)(void* data);
void call_in_the_future(future_cb cb, void* data) {
  cb(data);
}

struct Mine { /* For sake of the example */ };

struct Wrapper {
  Wrapper() : ptr_(nullptr) { }

  template <typename DataType>
  void run(void (*cb)(Wrapper* wrap, DataType other), DataType ptr) {
    auto lcb = [](void* data) {
      Wrapper* wrap = static_cast<Wrapper*>(data);
      DataType other = static_cast<DataType>(wrap->ptr_);

      /***** Compiler error here *****/
      cb(wrap, other);
    };

    ptr_ = ptr;

    // Call the hypothetical asynchronous C library function.
    call_in_the_future(lcb, this);
  }

  void* ptr_;
};

// Looking to store each type and cast them for this callback.
static void wrapper_cb(Wrapper* wrap, Mine* me) { }

int main() {
  Mine* me = new Mine();
  Wrapper* wrap = new Wrapper();
  wrap->run(wrapper_cb, me);
}

Here are the build errors:

main5.cc:19:7: error: variable 'cb' cannot be implicitly captured in a lambda with no capture-default specified
      cb(wrap, other);
      ^
main5.cc:13:19: note: 'cb' declared here
  void run(void (*cb)(Wrapper* wrap, DataType other), DataType ptr) {
                  ^
main5.cc:14:16: note: lambda expression begins here
    auto lcb = [](void* data) {
               ^
main5.cc:19:7: error: variable 'cb' cannot be implicitly captured in a lambda with no capture-default specified
      cb(wrap, other);
      ^
main5.cc:37:9: note: in instantiation of function template specialization 'Wrapper::run<Mine *>' requested here
  wrap->run(wrapper_cb, me);
        ^
main5.cc:13:19: note: 'cb' declared here
  void run(void (*cb)(Wrapper* wrap, DataType other), DataType ptr) {
                  ^
main5.cc:14:16: note: lambda expression begins here
    auto lcb = [](void* data) {
               ^
2 errors generated.

EDIT: There's a compile error if I try to capture cb in the lambda:

error: no matching function for call to 'call_in_the_future'

EDIT 2: To make it clear, I understand why these two build errors are happening. What I'm wondering is if there's a way around these issues so I can call cb.

Trevor Norris
  • 20,499
  • 4
  • 26
  • 28

2 Answers2

2

Your capture list is empty. cb is not passed in to the lambda. So it cannot be used.

auto lcb = [](void* data) {
  Wrapper* wrap = static_cast<Wrapper*>(data);
  DataType other = static_cast<DataType>(wrap->ptr_);

  /***** Compiler error here *****/
  cb(wrap, other);
};

Lambda expressions

The next error happens, because ONLY a lambda which doesn't capture anything can be used as a standrard function call.

C++ lambda with captures as a function pointer Passing lambda as function pointer

Robert Andrzejuk
  • 5,076
  • 2
  • 22
  • 31
  • I updated the post to show that it won't work to capture `cb` in the lambda. – Trevor Norris Aug 31 '17 at 14:15
  • I understand both of these issues. What I'm wondering is if there's a way around it. For example doing some magic with `std::function`. – Trevor Norris Aug 31 '17 at 15:04
  • Because capturing means that the lambda is no longer stateless. Lambdas that don't capture become static member functions of a compiler-generated class, which is why you can take a function pointer to them. It's the same with lambdas that do capture, except that they are *not* static (the captures are stored on member data variables) -- and this puts you back at square one, because the best you can do is take a *member* function pointer. – cdhowie Aug 31 '17 at 15:05
1

You have to store the callback also in the wrapper, something like:

struct Wrapper {
    template <typename DataType>
    void run(void (*cb)(Wrapper*, DataType*), DataType* ptr) {
        auto lcb = [](void* userdata) {
            Wrapper* wrap = static_cast<Wrapper*>(userdata);
            auto* cb = reinterpret_cast<void(*)(Wrapper*, DataType*)>(wrap->cb_);
            DataType* other = static_cast<DataType*>(wrap->ptr_);

            cb(wrap, other);
        };

        cb_ = reinterpret_cast<void(*)()>(cb);
        ptr_ = ptr;

        // Call the hypothetical asynchronous C library function.
        call_in_the_future(lcb, this);
    }

    void (*cb_)() = nullptr; // Cannot use void* to erase type of function pointer
    void* ptr_ = nullptr;
};

or without cast for the callback:

class Wrapper {
private:
    class IFunc
    {
    public:
        virtual ~IFunc() =  default;
        virtual void Call() const = 0;
    };

    template <typename DataType>
    struct Func : IFunc
    {
        Func(void (*cb)(Wrapper*, DataType*), DataType* ptr, Wrapper* wrapper) :
            cb(cb), ptr(ptr), wrap(wrapper) {}

        void Call() const override { cb(wrap, ptr); }

        static void CallFromVoidP(void* that)
        {
            static_cast<Func*>(that)->Call();
        }

        void (*cb)(Wrapper*, DataType*);
        DataType* ptr;
        Wrapper* wrap;
    };

public:
    template <typename DataType>
    void run(void (*cb)(Wrapper*, DataType*), DataType* ptr) {
        func = std::make_unique<Func<DataType>>(cb, ptr, this);

        // Call the hypothetical asynchronous C library function.
        call_in_the_future(&Func<DataType>::CallFromVoidP, func.get());
    }

    std::unique_ptr<IFunc> func;
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • that is amazing. i was poorly attempting something like that, but thought I read that this type of casting isn't safe. I'd like to verify, is there any UB here? – Trevor Norris Aug 31 '17 at 19:27
  • I find that answer [cast-a-function-pointer](https://stackoverflow.com/a/11240372/2684539) which says it is valid (as long as you cast back to its original type to use it). – Jarod42 Sep 01 '17 at 12:01