Consider this class hierarchy:
struct Animal { virtual ~Animal(); };
struct Cat : virtual Animal {};
struct Dog final : virtual Animal {};
My understanding is that putting final
on class Dog
ensures that nobody can ever create a class inheriting from Dog
, which, corollary, means that nobody can ever create a class that IS-A Dog
and IS-A Cat
simultaneously.
Consider these two dynamic_cast
s:
Dog *to_final(Cat *c) {
return dynamic_cast<Dog*>(c);
}
Cat *from_final(Dog *d) {
return dynamic_cast<Cat*>(d);
}
GCC, ICC, and MSVC ignore the final
qualifier and generate a call to __dynamic_cast
; this is unfortunate but not surprising.
What surprised me is that Clang and Zapcc both generate optimal code for from_final
("always return nullptr"), but generate a call to __dynamic_cast
for to_final
.
Is this really a missed optimization opportunity (in a compiler where obviously somebody put some effort into respecting the final
qualifier in casts), or is the optimization impossible in this case for some subtle reason that I'm still not seeing?
Ok, I dug through Clang's source code to find this method:
/// isAlwaysNull - Return whether the result of the dynamic_cast is proven
/// to always be null. For example:
///
/// struct A { };
/// struct B final : A { };
/// struct C { };
///
/// C *f(B* b) { return dynamic_cast<C*>(b); }
bool CXXDynamicCastExpr::isAlwaysNull() const
{
QualType SrcType = getSubExpr()->getType();
QualType DestType = getType();
if (const PointerType *SrcPTy = SrcType->getAs<PointerType>()) {
SrcType = SrcPTy->getPointeeType();
DestType = DestType->castAs<PointerType>()->getPointeeType();
}
if (DestType->isVoidType()) // always allow cast to void*
return false;
const CXXRecordDecl *SrcRD =
cast<CXXRecordDecl>(SrcType->castAs<RecordType>()->getDecl());
//********************************************************************
if (!SrcRD->hasAttr<FinalAttr>()) // here we check for Final Attribute
return false; // returns false for Cat
//********************************************************************
const CXXRecordDecl *DestRD =
cast<CXXRecordDecl>(DestType->castAs<RecordType>()->getDecl());
return !DestRD->isDerivedFrom(SrcRD); // search ancestor types
}
I'm getting a little tired from parsing code, but it doesn't seem to me that your from_final
is not simply always null because of the Final Attribute, but in addition because that Cat
is not in the Dog
derived record chain. Granted, if it didn't have the final
attribute, then it would have exited early (as it does when Cat
is a Src), but it wouldn't have necessarily been always null.
I'm guessing there are a couple reasons for this. The first is that dynamic_cast casts both up and down the record chain. When the Source Record has the Final Attribute, you can simply check the chain if Dest is an ancestor (because there can be no derived classes from Source).
But what if the class is not final? I suspect there might be more to it. Maybe multiple inheritance makes searching up casts more difficult than down casts? Without stopping the code in a debugger, all I can do is speculate.
This I do know: isAlwaysNull
is an early exiting function. It's a reasonable assertion that it's trying to prove that the result is always null. You can't prove a negative (as they say), but you can disprove a positive.
Perhaps you can check the Git history for the file and email the person who wrote that function. (or at least added the final
check).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With