Recently, I've come across a couple of type-erasure implementations that use a "hand-rolled" vtable - Adobe ASL's any_regular_t
is one example, although I've seen it used in Boost ASIO, too (for the completion routine queue).
Basically, the parent type is passed a pointer to a static type full of function pointers defined in the child type, similar to the below...
struct parent_t;
struct vtbl {
void (*invoke)(parent_t *, std::ostream &);
};
struct parent_t {
vtbl *vt;
parent_t(vtbl *v) : vt(v) { }
void invoke(std::ostream &os) {
vt->invoke(this, os);
}
};
template<typename T>
struct child_t : parent_t {
child_t(T val) : parent_t(&vt_), value_(val) { }
void invoke(std::ostream &os) {
// Actual implementation here
...
}
private:
static void invoke_impl(parent_t *p, std::ostream &os) {
static_cast<child_t *>(p)->invoke(os);
}
T value_;
static vtbl vt_;
};
template<typename T>
vtbl child_t<T>::vt_ = { &child_t::invoke_impl };
My question is, what is the advantage of this idiom? From what I can tell, it's just a re-implementation of what a compiler would provide for free. Won't there still be the overhead of an extra indirection when parent_t::invoke
calls vtbl::invoke
.
I'm guessing that it's probably got something to do with the compiler being able to inline, or optimize out the call to vtbl::invoke
or something, but I'm not comfortable enough with Assembler to be able to work this out myself.