Problem
I've got a C-like API, that I don't have control over, with functions to register/unregister event callbacks:
enum Event { Evt1, Evt2, Evt3 }; // events generated by API library
typedef void(__cdecl *Callback)(Event e, void* context);
void API_add_callback(Event e, Callback cb, void* context);
void API_remove_callback(Event e, Callback cb, void* context);
I went to create a wrapper base class APIClient
to encapsulate this API like so:
class APIClient
{
public:
APIClient(){}
protected:
// this is to be used by subclasses
void subscribe(const set<Event>& events)
{
_events = events;
set<Event>::const_iterator it;
for (it = _events.begin(); it != _events.end(); ++it)
{
API_add_callback(e, &callback, this);
}
}
// this is to be used by subclasses
void unsubscribe()
{
set<Event>::const_iterator it;
for (it = _events.begin(); it != _events.end(); ++it)
{
API_remove_callback(e, &callback, this);
}
}
// this is to be implemented by subclasses
virtual void on_event(Event e) = 0;
private:
// this is the proxy callback that we register for all events
static void __cdecl callback(Event e, void* context)
{
APIClient* instance = (APIClient*)context;
// forward the event to the subclass
instance->on_event(e);
}
set<Event> _events;
};
So far so good, I thought. Then I made two subclasses, Foo
and Bar
that are APIClient
s:
// This one is interested in Evt1 and Evt2 of the API...
class Foo : public APIClient
{
public:
Foo() : APIClient()
{
set<Event>s;
s.insert(Evt1);
s.insert(Evt2);
subscribe(s);
}
~Foo()
{
unsubscribe();
}
protected:
virtual void on_event(Event e)
{
// here e will be Evt1 or Evt2, whenever they are fired
// by the API
}
};
// And this one is interested in Evt2 and Evt3 of the API...
class Bar : public APIClient
{
public:
Bar() : APIClient()
{
set<Event>s;
s.insert(Evt2);
s.insert(Evt3);
subscribe(s);
}
~Bar()
{
unsubscribe();
}
protected:
virtual void on_event(Event e)
{
// here e will be Evt2 or Evt3, whenever they are fired
// by the API
}
};
Trouble is, it doesn't work ... because the library behind the API determines a unique subscription based on event and callback, not the context (the context is just additional, optional, user data). So, in general, as it turned out, after
API_add_callback(Evt2, &callback, instance_of_Foo);
API_add_callback(Evt2, &callback, instance_of_Bar);
only the second subscription wins, so Foo
never hears of Evt2
.
My failed solution attempt
Since is appears that the API requires a specific callback (i.e. a different address) for every new subscription for the same event I thought: code generation ... templates!
After templatizing APIClient, something like &APIClient<Foo>::callback
and &APIClient<Bar>::callback
should give me different addresses, right? Wrong! It only generates different addresses (that is, different functions) if they're sufficiently different.
So
template<typename T>
class APIClient
{
// ... other code ...
static void __cdecl callback(Event e, void* context)
{
APIClient* instance = (APIClient*)context;
// forward the event to the subclass
instance->on_event(e);
}
}
is no good. But the following would force a template instantiation for T=Foo
and T=Bar
, thus giving me &APIClient<Foo>::callback != &APIClient<Bar>::callback
:
template<typename T>
class APIClient
{
// ... other code ...
static void __cdecl callback(Event e, void* context)
{
APIClient* instance = (APIClient*)context;
// Use T here explicitely to force different template
// instantiations of APIClient<T>::callback
T::call_something();
// forward the event to the subclass
instance->on_event(e);
}
}
This is no good. It looks like I'm trying to outsmart the compiler to make it generate what seems to be redundant code, and I think I am doomed to fail :)
Question (finally)
- Is there a clean way (without singletons and all) to solve my original problem?
- Is there a way to do it the template way, making sure that I get different functions for each instantiation of
APIClient<T>::callback
without doing anything ugly?
Note: C++11 is ruled out, unfortunately.