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.

Shawn
  • 47,241
  • 3
  • 26
  • 60
Amirul I
  • 191
  • 3
  • 15
  • 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. – Tony Delroy May 29 '19 at 02: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.) – Tony Delroy May 29 '19 at 02: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? – chris May 29 '19 at 03: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. – Amirul I May 29 '19 at 03: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. – chris May 29 '19 at 03:12
  • @chris oh the registering will happen multiple times (maybe 3/4 times, but the number is known before execution of the code). – Amirul I May 29 '19 at 03:27
  • Related [question](https://stackoverflow.com/questions/400257/how-can-i-pass-a-class-member-function-as-a-callback). – 1201ProgramAlarm May 29 '19 at 03:50
  • Possible duplicate of [C++ class member function callback](https://stackoverflow.com/questions/8079453/c-class-member-function-callback) – Raymond Chen May 29 '19 at 03:53

1 Answers1

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524