Skip to content

Comment on Visualizing Rust's Vtables: How dyn Trait Works In Memoryparent

Comments

Everything. The issue is that the compiler won't even bother with polymorphism through a vtable for a polymorphic type (one with a vtable), unless the object is accessed through a pointer or reference.

If you have a value of the type itself (not a pointer or reference), then polymorphism doesn't even enter the equation in C++, even if you initialize from a derived type.

E.g. in this code:

    Base b(m_catalog.makeDerived());
    b.call_virt_func();
Even if `call_virt_func` is declared virtual, it will be `Base::call_virt_func()` that is called here, guaranteed. From a language perspective, we already know that `b` is a `Base`, you literally declared and defined it that way.

Runtime polymorphism is therefore only a game for pointers or references; it is the process of resolving the indirection that even allows for polymorphism to become a thing in C++. But this means that the compiler cannot know the actual type at compile-time for a polymorphic type, unless it can perform devirtualization as an optimization pass.

So although C++ will certainly allow you to define a class method that returns a virtual type by value (and not by pointer or reference), even for complex types, it is almost certainly a bug to do this unless you know for sure what the type will be statically, at compile time. Because the object you create as the return value will be forced to be the return type declared at compile time, "forgetting" the fact that it was created from a type deeper in the inheritance chain. This is the 'slicing problem' that was mentioned in the earlier comment.

Ah. Dyno (https://github.com/ldionne/dyno) is good at this stuff. If you have types Base, Child1, Child2, you can just return a Dyno object that can be any of these, expressed as a tagged union and not a Box-equivalent, and then do regular vtable-based or otherwise polymorphic dispatch into the object. You can also arrange it so that if you have a Child3 that can't fit in the (Base, Child1, Child2) union, the Child3 can be heap-allocated and invoked transparently as well. It's open-world type erasure.

C++ is so freakishly powerful is that it can not only solve this problem, but it can solve it via a regular library and not a language extension.

Dyno is really freakish and I can see a couple of pain points if someone tries to use it productively. A IMO glaring one is that dyno::poly is a boxing type that will happily take your value type and shove it on the heap while nobody's looking. This is a defect similar to the silent heap allocations for captured parameters in std::function.

On the other hand, it's really impressive that one can push dynamic function pointer lookup tables in C++ to a point where it looks almost, but maybe not quite like the real thing.

It is the real thing. Not "almost". The actual thing. If you want to forestall accidental heap policy, make a storage policy that prohibits it.

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.