If you have to use dynamic_cast I would recommend using pointers instead of references. And to make the API a bit clearer I would use unique_ptr or shared_ptr whenever you're dealing with owning pointers and only use raw pointers when you give temporary access to the objects, like when passing them as parameters to functions (that shouldn't claim ownership). This would aid in communicating whenever ownership is transfered (unique_ptr) or expected to be (possible) shared (shared_ptr).
Another thing to keep in mind is that not all builds enabled RTTI (runtime type information) which is required for dynamic_cast. Objects can usually be designed in such a way that you never have to retrieve the exact type of object.
However, to make your code compile I have three alternative versions.
With references
struct Base { virtual ~Base() {} };
struct Derived : Base { int x = 5; };
Derived d = {};
const Base& get() {
return d;
}
int main() {
const Base& b = get();
const Derived& a = *dynamic_cast<const Derived*>(&b); // This would be undefined behaviour if b wasn't of type Derived as dynamic_cast would return null
}
With unique_ptr
#include <memory>
struct Base { virtual ~Base() {} };
struct Derived : Base { int x = 5; };
std::unique_ptr<const Base> get() {
return std::make_unique<const Derived>();
}
int main() {
std::unique_ptr<const Base> b = get();
const Derived* d = dynamic_cast<const Derived*>(b.get());
}
With shared_ptr
#include <memory>
struct Base { virtual ~Base() {} };
struct Derived : Base { int x = 5; };
std::shared_ptr<const Base> get() {
return std::shared_ptr<const Derived>();
}
int main() {
std::shared_ptr<const Base> b = get();
std::shared_ptr<const Derived> d = std::dynamic_pointer_cast<const Derived>(b);
}