In my code base I have a polymorphic class that just fails to work with dynamic_cast under certain circumstances; rtti seems to be ‘off’ in a template function? I’m trying to get to the bottom of this but I’m quite stuck right now. I’ve minified the situation to it’s essentials, and what it basically boils down to is that:
- I have a base class Base and a derived class Derived.
- Base has a virtual destructor and a pure virtual function to be filled in, so it def. has a vtable.
- I
new
a Derived and put in in aBase*
. - If I then directly afterwards try a
dynamic_cast<Derived*>(basePtr)
, I correctly get the ptr-to-Derived. - However, because reasons in my original code base, I do the dynamic_cast in a template function
bool GetTypedPtr(T*& ptr)
. And here the trouble starts. - This code works correctly and has always done so under Visual Studio on Windows.
- This code works correctly and has always done so with an existing build configuration under Xcode.
- This code now fails with a new but identical build configuration under the same Xcode.
- The culprit seems to be the type of the templated arg? Even if I override it myself.
- If I change the overridden pure virtual method in Derived to be an inline empty function body in the .h instead of specifying one in it’s .cpp, the problem goes away. What gives…
I’ve instrumented the code with typeid
calls on the incoming pass-by-ref out arg, the hardcoded Derived type, and the template typename used. Their .name is all identical (the same mangled name from clang). Their .hash_code isn’t though, and the template type is the one that is off. This undoubtedly is the cause dynamic_cast fails with a nullptr result.
What could be going on here? It seems like this is such a fundamental language construct that I should rule out failing compiler optimizations and such. It can’t also have anything to do with the pass-by-ref (with the ref messing with the template type deduction). I’m using Xcode 12.3 btw with clang 12.0.0 — old, I know, but it’s the only one I have access to right now. And I’m using c++17 std compliant mode.
I’m… lost with this one. I’ve listed an MRE below.
Base.h
#pragma once
class Base {
public:
Base() = default;
virtual ~Base() = default;
virtual void DoIt() = 0;
};
Derived.h
#pragma once
#include "Base.h"
class Derived : public Base {
public:
void DoIt() override; // dynamic_cast fails when like this
//void DoIt() override {}; // dynamic_cast works when like this and without the .cpp
};
Derived.cpp
#include "Derived.h"
void Derived::DoIt() {}
TestCode.h
#pragma once
void TestCode();
TestCode.cpp
#include "TestCode.h"
#include "Derived.h"
#include "Base.h"
#include <cassert>
#include <typeinfo>
template <typename DerivedType>
void GetTypedV1(Base* basePtr, DerivedType*& derivedPtr) {
auto& derivedClassType = typeid(Derived);
auto& templateClassType = typeid(DerivedType);
assert(derivedClassType.hash_code() == templateClassType.hash_code()); // succeeds
derivedPtr = dynamic_cast<DerivedType*>(basePtr);
assert(derivedPtr != nullptr); // fails?!
}
template <typename DerivedType>
DerivedType* GetTypedV2(Base* basePtr) {
auto& derivedClassType = typeid(Derived);
auto& templateClassType = typeid(DerivedType);
assert(derivedClassType.hash_code() == templateClassType.hash_code()); // succeeds
Derived* derivedPtr = dynamic_cast<DerivedType*>(basePtr);
assert(derivedPtr != nullptr); // fails?!
return derivedPtr;
}
void TestCode() {
Base* basePtr = new Derived;
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
assert(derivedPtr != nullptr); // succeeds
// original failing code:
GetTypedV1(basePtr, derivedPtr);
GetTypedV1<Derived>(basePtr, derivedPtr);
derivedPtr = GetTypedV2<Derived>(basePtr);
}