Background
I had come across and application where I had access to a method:
void AttachCallback(int event, std::functional<void(int)> cb);
which allowed me to attach a callback cb
to event event
. I also had several classes that would need to attach callbacks to a couple events in their constructors and implement the callback for each event they bound to. So the first implementation that comes to mind looks like:
class MyClass {
public:
MyClass() {
AttachCallback(0, [this](int value) { cb0(value); });
AttachCallback(2, [this](int value) { cb2(value); });
// ... as necessary
}
private:
void cb0(int value) { /*... callback for event 0 ...*/ }
void cb2(int value) { /*... callback for event 2 ...*/ }
// ... as necessary
};
But, since I've been playing around with templates a lot recently, I wondered if I could create a templated pure-virtual class for listening to a given event and use it as follows:
template<const int EVENT>
class ListensToEvent {
public:
virtual ~ListensToEvent() = default;
protected:
ListensToEvent() {
AttachCallback(EVENT, [this](int value) { cb(value); });
}
virtual void cb(int value) = 0;
};
class MyClass : public ListensToEvent<0>, public ListensToEvent<2> {
private:
void cb(int value) override;
};
template<> // This line makes me suspicious
void MyClass::ListensToEvent<0>::cb(int value) {
/*... callback for event 0 ...*/
}
template<>
void MyClass::ListensToEvent<2>::cb(int value) {
/*... callback for event 2 ...*/
}
When searching around for related topics, I found this thread which shows how to use helper classes to remedy clashes that occur when two base interfaces provide the same identifier pure-virtual method. Since I use templates here, I wouldn't be able to use helpers like that because I don't know how many instantiations there could be, but I found that declaring the method without specialization and then providing template specializations outside the class definition allowed me to target each base classes' method individually (as shown above).
Proof of Concept
While testing this out, I built a simplification of this design (without callbacks) just to show that it compiles and specializes here.
Question
Primarily, I'm concerned with my understanding of why this works. It makes sense to me that I would be able to specify which cb
I'm implementing after the class definition by qualifying cb
with either ListensToEvent<0>
or ListensToEvent<2>
. What I don't understand is why that qualification counts as a template specialization and therefore why the line template<>
is needed. Additionally, if these qualifications are in fact template specializations, how exactly does C++ view the method specializations and why do they work?
With that said, I'm also interested in comments about the functionality of this design. Is it an efficient way to simplify MyClass and it's other siblings implementations, or would it have been better done the first way I proposed? Or is there another design that would work best in this situation?