blob: 983d3156842456f0e60a53c340a4c080144bb241 [file]
/*
* Copyright 2023 Rive
*/
// "lite_rtti_cast<T*>()" is a very basic polyfill for "dynamic_cast<T*>()" that
// does not require all of the heavyweight C++ RTTI to be enabled in the
// compiler. The root of the RTTI chain must derive from enable_lite_rtti, and
// any subclasses must inherit from lite_rtti_override (using the provided
// macros)
//
// class Root : public ENABLE_LITE_RTTI(Root) {};
// class Derived : public LITE_RTTI_OVERRIDE(Root, Derived) {};
// Root* derived = new Derived();
// lite_rtti_cast<Derived*>(derived);
//
#pragma once
#include "utils/compile_time_string_hash.hpp"
#include "rive/refcnt.hpp"
#include <cassert>
#include <stdint.h>
#include <type_traits>
namespace rive
{
namespace detail
{
// This helper is called by lite_rtti_cast (and related functions) to check
// whether the cast is allowed.
template <typename Dst, typename Src> bool canRTTICastTo(Src* src)
{
using D = std::remove_pointer_t<std::remove_reference_t<Dst>>;
static_assert(
std::is_base_of_v<Src, D> || std::is_base_of_v<D, Src>,
"lite RTTI casts should only be used between related types");
static_assert(
std::is_base_of_v<Src, D>,
"lite RTTI casts should only be used for downcasts, otherwise use static_cast");
static_assert(
!std::is_same_v<std::remove_const_t<Src>, std::remove_const_t<D>>,
"lite RTTI casts don't need to be used for casting a type to itself");
static_assert(
std::is_const_v<D> || !std::is_const_v<Src>,
"cannot remove const during a lite RTTI cast");
return src != nullptr && src->canRTTICastTo(D::TypeIDForRTTI);
}
} // namespace detail
template <uint32_t ID> class enable_lite_rtti
{
private:
// We don't allow casts from same type to same type so this will never return
// true, as it's the root and can't be downcast to.
virtual bool canRTTICastTo(uint32_t id) const { return false; }
template <class, uint32_t> friend class lite_rtti_override;
template <typename Dst, typename Src>
friend bool detail::canRTTICastTo(Src* src);
#ifdef WITH_RIVE_TOOLS
public: // For unit tests, make this public
#endif
static constexpr uint32_t TypeIDForRTTI = ID;
};
// lite_rtti_override is the class for any derived types.
template <class Base, uint32_t ID> class lite_rtti_override : public Base
{
public:
// import all parent-class constructors
using Base::Base;
virtual ~lite_rtti_override() = default;
private:
bool canRTTICastTo(uint32_t id) const override
{
return ID == id || Base::canRTTICastTo(id);
}
template <class, uint32_t> friend class lite_rtti_override;
template <typename Dst, typename Src>
friend bool detail::canRTTICastTo(Src* src);
#ifdef WITH_RIVE_TOOLS
public: // For unit tests, make this public
#endif
static constexpr uint32_t TypeIDForRTTI = ID;
};
// Like dynamic_cast<>, but opt-in, without requiring C++ RTTI to be enabled.
template <class DstPtr, class Src> DstPtr lite_rtti_cast(Src* src)
{
static_assert(
std::is_pointer_v<DstPtr>,
"lite_rtti_cast should be done to a pointer type");
return detail::canRTTICastTo<DstPtr>(src) ? static_cast<DstPtr>(src)
: nullptr;
}
// Like lite_rtti_cast<>, but asserts that the cast succeeded.
template <class DstPtr, class Src> DstPtr lite_rtti_cast_or_assert(Src* src)
{
DstPtr dst = lite_rtti_cast<DstPtr>(src);
assert(dst != nullptr);
return dst;
}
template <class Dst, class Src> rcp<Dst> lite_rtti_rcp_cast(rcp<Src> src)
{
return detail::canRTTICastTo<Dst>(src.get()) ? static_rcp_cast<Dst>(src)
: nullptr;
}
// Different versions of clang-format disagree on how to format these.
// clang-format off
#define ENABLE_LITE_RTTI(ROOT) enable_lite_rtti<CONST_ID(ROOT)>
#define LITE_RTTI_OVERRIDE(BASE, DERIVED) \
lite_rtti_override<BASE, CONST_ID(DERIVED)>
#define LITE_RTTI_CAST_OR_RETURN(NAME, TYPE, POINTER) \
auto NAME = rive::lite_rtti_cast<TYPE>(POINTER); \
if (NAME == nullptr) \
return
#define LITE_RTTI_CAST_OR_BREAK(NAME, TYPE, POINTER) \
auto NAME = rive::lite_rtti_cast<TYPE>(POINTER); \
if (NAME == nullptr) \
break
#define LITE_RTTI_CAST_OR_CONTINUE(NAME, TYPE, POINTER) \
auto NAME = rive::lite_rtti_cast<TYPE>(POINTER); \
if (NAME == nullptr) \
continue
// clang-format on
} // namespace rive