2

I am having a bit trouble understanding how to pass this to a callback function which is passed to a library. The function needs to have a specific signature.

the library in question is OCILIB (https://vrogier.github.io/ocilib/doc/html/classocilib_1_1_subscription.html) and trying to pass a class function as the 4th parameter of Register().

It is possible for me to pass it as following without problem

&database::callback // a static function of database

or

[](ocilib::Event &event) // as lambda function
{
}

but it does not have the access to instance variables. I tried to use it like

[&](ocilib::Event &event) // as lambda function
{
}

but the signature do not match, and I get the following error

database.cpp: In member function ‘bool dcn::database::watch(std::__cxx11::string)’:
database.cpp:104:44: error: no matching function for call to ‘ocilib::Subscription::Register(ocilib::Connection&, std::__cxx11::string&, ocilib::Subscription::ChangeTypesValues, dcn::database::watch(std::__cxx11::string)::<lambda(ocilib::Event&)>, unsigned int, unsigned int)’
    }, (unsigned int) 7778, (unsigned int) 0);
                                            ^
In file included from /usr/include/ocilib.hpp:9194:0,
                 from /home/ai/dcn/include/main.h:17,
                 from database.cpp:1:
/usr/include/ocilib_impl.hpp:6650:13: note: candidate: void ocilib::Subscription::Register(const ocilib::Connection&, const ostring&, ocilib::Subscription::ChangeTypes, ocilib::Subscription::NotifyHandlerProc, unsigned int, unsigned int)
 inline void Subscription::Register(const Connection &connection, const ostring& name, ChangeTypes changeTypes, NotifyHandlerProc handler, unsigned int port, unsigned int timeout)
             ^~~~~~~~~~~~
/usr/include/ocilib_impl.hpp:6650:13: note:   no known conversion for argument 4 from ‘dcn::database::watch(std::__cxx11::string)::<lambda(ocilib::Event&)>’ to ‘ocilib::Subscription::NotifyHandlerProc {aka void (*)(ocilib::Event&)}’
make[1]: *** [database.o] Error 1

function is defined as

static void callback(ocilib::Event &);

Need your help to untangle this. thanks in advance.

8
  • 1
    Yeah - classic problem. OCILIB is only letting you register a void (*)(ocilib::Event&) for callbacks, and that can only be matched with a static member or standalone function. You need to write such a function that internally consults some records to work out which event to invoke a member function on. For example, it might look inside the ocilib::Event argument during the callback, then use some id to look up an associative container (map, unordered_map) to find a pointer to the object to invoke a non-static member function on. Commented May 29, 2019 at 2:55
  • (If OCILIB's Subscription::Register were rewritten in modern C++, it should change to a std::function argument instead of a non-member-function-pointer argument, and that would allow you to pass lambdas with captures.) Commented May 29, 2019 at 2:57
  • If the API doesn't give you the option of passing through a void*, there's not a whole lot you can do without resorting to less-than-ideal solutions. Could you give some context regarding this call? Can the code making the call be run multiple times in the same program execution? Commented May 29, 2019 at 3:02
  • @chris yes it will be called many times. the callback is part of a hook which will be waiting for changes in the database and copying to a different database. Commented May 29, 2019 at 3:09
  • I don't mean the callback itself, but the code registering the callback. There are different levels of hacky solutions and one is to generate a function pointer that would be overwritten if the registration code is called again. Other levels are varying degrees of more work and cover having fewer or weaker assumptions. Commented May 29, 2019 at 3:12

1 Answer 1

7

That is a horrible API. Anyone who has a callback function without also asking for a void* is writing code we knew was a bad idea in the 70s.

You have no choice but to use global state to dispatch back to your class.

template<std::size_t N, class R, class...Args>
struct crappy_api_fix{
  static std::array< std::function<R(Args&&...)>, N >& table(){
    static std::array< std::function<R(Args&&...)>, N > arr;
    return arr;
  }
  template<std::size_t I>
  static R call( Args...args ){ return table()[I]( std::forward<Args>(args)... ); }
  using sig=R(Args...);
  template<std::size_t I=N-1>
  static sig* make(std::function<R(Args&&...)> f){
    if(!table()[I]){
      table()[I]=f;
      return &call<I>;
    }
    if(I==0) return nullptr;
    return make< (I-1)%N >(f);
  }
  template<std::size_t I=N-1>
  static void recycle( sig* f ){
    if (f==call<I>){
      table()[I]={};
      return;
    }
    if (I==0) return;
    recycle< (I-1)%N >( f);
  }
};

something like that. It maintains a global table of N std functions, and returns a function pointer that knows which std function to call.

// up to 50 callbacks alive at once:
using cb_helper = crappy_api_fix<50, void, ocilib::Event &>;

// "stateless" function pointer wrapping a lambda:
void(*f)(ocilib::Event&) = cb_helper::make([&](ocilib::Event &event) {});

// blah is the API class with the Register method.  We pass the f from above:
blah->Register(arg1, arg2, f, arg4);

// This might be far away fron the above code:
// we should keep copy of the f; when we have unregistered, we shoukd recyle it:
cb_helper::recycle(f); // when `f` is no longer needed

Apologies for typos, typing this on my phone.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.