Is there any problem in performing dynamic casting via the following function?
template<typename Base, typename Derived>
requires std::is_convertible_v<Derived&, Base&> &&
std::is_polymorphic_v<Base>
inline std::unique_ptr<Derived> cast_to(std::unique_ptr<Base>&& ptr)
{
return std::unique_ptr<Derived>(dynamic_cast<Derived*>(ptr.release()));
}
Yes, your function can easily leak memory if the cast fails. Consider the following:
struct Base
{
virtual ~Base() = default;
};
struct Derived1 : Base {};
struct Derived2 : Base {};
int main()
{
std::unique_ptr<Base> bp = std::make_unique<Derived1>();
// bp does not point to a Derived2, so this cast will fail
auto d2p = cast_to<Base, Derived2>(std::move(bp));
std::cout << bp.get() << '\n'; // 0
std::cout << d2p.get() << '\n'; // also 0; object was leaked
}
From this snippet you can also see another small issue: because of the order of the template parameters you have to supply them both. You can't let the compiler deduce Base
because it comes before Derived
.
With both of those issues in mind, the following would be a better implementation:
template<typename Derived, typename Base> // swap the order of template parameters
requires std::is_convertible_v<Derived&, Base&> &&
std::is_polymorphic_v<Base>
inline std::unique_ptr<Derived> cast_to(std::unique_ptr<Base>&& ptr)
{
Derived* d = dynamic_cast<Derived*>(ptr.get());
if (d) {
ptr.release();
return std::unique_ptr<Derived>(d);
}
return nullptr; // object is still owned by ptr
}
This fixes both of the above issues:
int main()
{
std::unique_ptr<Base> bp = std::make_unique<Derived1>();
// No need to explicitly specify Base; the compiler can deduce that itself
auto d2p = cast_to<Derived2>(std::move(bp));
std::cout << bp.get() << '\n'; // not 0; no leak
std::cout << d2p.get() << '\n'; // 0
}