Working Around C++ Virtual Template Issues
Today I had a real problem, and I didn't even know it. It took me to the point of getting it all written up to realize that C++ virtual methods in template classes just aren't really possible. I ended up doing a big search on google, and there are enough examples of people trying to do this that I was convinced that it really isn't possible. And that left me in a lurch.
The original design I was working with in DKit was a source and sink, as I've written about in the past. What I did with these classes was to make them template classes, just recently realizing that all their method implementations needed to be in the header, but the idea was that there would be a template for the source and sink to be used as the base class of a series of sources and sinks moving different values around.
In order to do this, the sources would allow the sinks to be registered as listeners, and the sinks would then me messaged when a source needed to send a message. The source template class method for adding a listener looked like this:
namespace dkit { template <class T> class source { public: virtual bool addToListeners( sink<T> *aSink ) { bool added = false; // first, make sure there's something to do if (aSink != NULL) { // next, see if we can add us as a source to him if (aSink->addToSources((const source<T>*)this)) { // finally, make sure we can add him to us added = addToSinks(aSink); } } return added; } }; } // end of namespace dkit
and then the code for sending a 'T' to all the registered listeners looked like this:
virtual bool send( const T anItem ) { bool ok = true; if (_online) { // lock this up for running the sends boost::detail::spinlock::scoped_lock lock(_mutex); // for each sink, send them the item and let them use it BOOST_FOREACH( sink<T> *s, _sinks ) { if (s != NULL) { if (!s->recv(anItem)) { ok = false; } } } } return ok; }
While I've got the virtual identifier in the template - I've learned that this is OK - so long as we're still dealing with template class definitions. So they stay, but the problem was soon approaching.
When I want to make a sink for the particular source I've created (we'll get to that in a minute), I need to subclass the sink which has the one important method:
namespace dkit { template <class T> class sink { public: virtual bool recv( const T anItem ) { /** * Here's where we need to process the item and do what we * need to with it. We need to be quick about it, though, * as the source is waiting on us to finish, and send the * same item to all the other registered listeners. */ return true; } }; } // end of namespace dkit
This method is called in the source's send() method, and is the way we'll be able to get what we need. My original plan was to implement the listener like this:
template <class T> class MySink : public dkit::sink<datagram*> { public: MySink() { } virtual bool recv( const datagram *anItem ) { // do something with 'anItem' - the datagram pointer return true; } };
but while it compiled, it didn't work. Why not? Because there is no support for virtual methods in partially implemented template classes. None.
No matter what I did, there was no getting around it. My test app was simple:
int main(int argc, char *argv[]) { bool error = false; MySink dump; udp_receiver rcvr(multicast_channel("udp://239.255.0.1:30001")); rcvr.addToListeners(&dump); rcvr.listen(); while (rcvr.isListening()) { sleep(1); } std::cout << (error ? "FAILED!" : "SUCCESS") << std::endl; return (error ? 1 : 0); }
I created the sink - called MySink, and then wired it up to the UDP receiver (a source), and started listening. All the datagrams were sent to the base class implementation of sink::recv(). Nothing was coming to the subclass' method.
OK, it's just not in the language. Boy, I sure wish it were, but it's not. So how to fix things? Well… I have a FIFO template class in DKit, and it's got working virtual methods. But the trick there is that the subclasses are still template classes. There's no attempt at specialization. So maybe the answer is as simple: Don't specialize!
So if I don't specialize, then my MySink class is still a template class:
template <class T> class MySink : public dkit::sink<T> { public: MySink() { } virtual bool recv( const T anItem ) { return onMessage(anItem); } };
and I've introduced a new function: onMessage(T). This, I just need to create for the kind of data I'm moving:
bool onMessage( const datagram *dg ) { if (dg == NULL) { std::cout << "got a NULL" << std::endl; } else { std::cout << "got: " << dg->contents() << std::endl; } return true; }
Now my test application has to include the type in the definition:
int main(int argc, char *argv[]) { bool error = false; MySink<datagram*> dump; udp_receiver rcvr(multicast_channel("udp://239.255.0.1:30001")); rcvr.addToListeners(&dump); rcvr.listen(); while (rcvr.isListening()) { sleep(1); } std::cout << (error ? "FAILED!" : "SUCCESS") << std::endl; return (error ? 1 : 0); }
What I'm doing is keeping the template subclass as the same template so that the virtual methods are going to work for us. Then I make use of the fact that I only need to make the one version of the method I need, as the templates are resolved at compile time. If I need a new subclass, then so be it. But I can make these implementations very easily and very quickly, and it's nearly as good as the fully virtual template methods - just one small step in there to glue things together.
I'm pleased with the progress. I'll be checking all this into GitHub soon, and then it'll be up there for good, but for now, I'm happy that I've figured out a clean way to make this work for me.
[5/25] UPDATE: I was working on this this morning, and there's a slightly cleaner way to do the polymorphic behavior with template classes:
template <class T> class MySink : public dkit::sink<T> { public: MySink() { } virtual bool recv( const T anItem ) { return onMessage(anItem); } bool onMessage( const datagram *dg ) { if (dg == NULL) { std::cout << "got a NULL" << std::endl; } else { std::cout << "got: " << dg->contents() << std::endl; } return true; } };
and at this point, it's all contained in the subclass of sink.
Minor point, but it's contained in one class, and that makes things a little easier to read and deal with. I tried to put the call in the sink superclass, but it's the same problem all over again - virtual methods in template classes are just not very well supported. But this at least is not too big of a hack to get things working properly.