I am reading a book on metaprogramming and there is secession on Trampolines:
struct generic_t
{
void* obj;
void(*del)(void*);
};
template <typename T> // outer template parameter
generic_t copy_to_generic(const T& value)
{
struct local_cast // local class
{
static void destroy(void* p) // void*-based interface
{
delete static_cast<T*>(p); // static type knowledge
}
};
generic_t p;
p.obj = new T(value); // information loss: copy T* to void*
p.del = &local_cast::destroy;
return p;
}
I totally understand how it works but I don't know what is the application of it! and where do you usually use this technique? dose anyone know about it? thanks :)
I use it in many places in my programs. One thing I like with this method is that you can hold a list of unrelated types. For example, I've seen a lot of code that looked like that:
struct Abstract { virtual ~Abstract() = default; };
template<typename P>
struct AbstractHandler : Abstract {
virtual void handle(P) = 0;
};
template<typename P, typename H>
struct Handler : AbstractHandler<P>, private H {
void handle(P p) override {
H::handle(p);
}
};
struct Test1 {};
struct Test1Handler {
void handle(Test1) {}
};
struct Test2 {};
struct Test2Handler {
void handle(Test2) {}
};
int main() {
std::vector<std::unique_ptr<Abstract>> handlers;
handlers.emplace_back(std::make_unique<Handler<Test1, Test1Handler>>());
handlers.emplace_back(std::make_unique<Handler<Test2, Test2Handler>>());
// some code later....
dynamic_cast<AbstractHandler<Test1>*>(handlers[0].get())->handle(Test1{});
dynamic_cast<AbstractHandler<Test2>*>(handlers[1].get())->handle(Test2{});
}
Dynamic casts add unnecessary overhead to the program. Instead, you could use type easure just like the one you've made to avoid this overhead.
Plus, there is no reason for Abstract
to even exist. It's an interface that expose no useful function. The real need here is to hold a list of unrelated interfaces.
Let's say we ajust type easure to allow copy_to_generic
to cast the instance to a parent class.
template <typename Parent, typename T>
generic_t to_generic(T&& value) // forward is better.
{
struct local_cast
{
static void destroy(void* p)
{
// we cast to the parent first, and then to the real type.
delete static_cast<std::decay_t<T>*>(static_cast<Parent*>(p));
}
};
generic_t p;
p.obj = static_cast<Parent*>(new std::decay_t<T>(std::forward<T>(value)));
p.del = &local_cast::destroy;
return p;
}
Look at this code with the type easure:
// No useless interface
template<typename P>
struct AbstractHandler {
// No virtual destructor needed, generic_t already has a virtual destructor via `del`
virtual void handle(P) = 0;
};
template<typename P, typename H>
struct Handler : private H {
void handle(P p) override {
H::handle(p);
}
};
struct Test1 {};
struct Test1Handler {
void handle(Test1) {}
};
struct Test2 {};
struct Test2Handler {
void handle(Test2) {}
};
int main() {
std::vector<generic_t> handlers;
handlers.emplace_back(
to_generic<AbstractHandler<Test1>>(Handler<Test1, Test1Handler>{})
);
handlers.emplace_back(
to_generic<AbstractHandler<Test2>>(Handler<Test2, Test2Handler>{})
);
// some code later....
static_cast<AbstractHandler<Test1>*>(handlers[0].obj)->handle(Test1{});
static_cast<AbstractHandler<Test2>*>(handlers[1].obj)->handle(Test2{});
}
No empty interface and no dynamic casts anymore! This code does the same thing as the other one, but faster.