- a graphical/visual notation for talking about base class hierarchies, including virtual bases, and
- a proof-of-concept "faster dynamic_cast" that does everything dynamic_cast does, but measurably faster than the Itanium-ABI runtime.
To Phil's original question "how do I make a dynamic_cast that casts only down, not sideways?"
After diagramming the situation in my head (using the notation from my talk), I'm like 90% sure that this approach necessarily works —
https://godbolt.org/z/cazrbPEx3I'm 99% sure it works if all the relationships involved are public. My 10% unsurety comes from the scenarios involving non-public inheritance relationships.
Oh, and it definitely doesn't work if the target type itself is two cats — e.g. this can't be used as-is to downcast `Cat` to `SiameseCat`. But that gives a nice hard compile-time error, rather than giving the wrong answer at runtime.
struct Cat { virtual ~Cat() = default; };
struct LeftCat : Cat {};
struct RightCat : Cat {};
struct SiameseCat : LeftCat, RightCat {};
LeftCat *downcast_to_leftcat(Cat *c) {
if (LeftCat *lc = dynamic_cast<LeftCat*>(c)) {
if (static_cast<Cat*>(lc) == c) {
return lc; // this was definitely a pure downcast
}
}
return nullptr;
}
int main() {
SiameseCat sc;
assert(downcast_to_leftcat((LeftCat*)&sc) != nullptr);
assert(downcast_to_leftcat((RightCat*)&sc) == nullptr);
}
Funny that you should be asking about that now. At this very moment I am writing a talk I will be giving at a couple conferences this fall, that includes this exact subject.
The sidecast offset is specifically needed with multiple inheritance because all the bases (except the first base) are NOT at the same "this" address as the derived class. So when you cast from the derived to one of the bases the pointer returned points specifically at the base object address. If you change the type but not the address you will be pointing at the wrong location (address of the first base class) using the wrong type (but not the type of the first base class). The cast computes the offset itself, however when you call via the virtual mechanism, the v-table points to a multiple inheritance thunking function that adjusts the "this" pointer address before calling the actual function to the start address of the base object.
Chris++;
dynamic_cast will both downcast and sidecast. (And upcast, etc.)
Is there some trick that can be used to constrain it to do one but not
the other?
If not, should the language provide additional casts that are more constrained
in what they do? (I.e. downcast<T>(expr), upcast<T>(expr), sidecast<T>(expr).)
Would that be feasible with how the current ABIs work?
I ask because, as you can guess, I've just hit an issue where I was getting
unexpected sidecasting where code was only expecting downcasting. My
most derived class has multiple inheritance (but not virtual
inheritance). I
invoke some function on one of its base classes, and that unexpectedly
behaves differently because it can cast itself to a sibling base class.
Thoughts anyone?