diff --git a/cetlvast/suites/unittest/test_rtti.cpp b/cetlvast/suites/unittest/test_rtti.cpp index 559c4799..751b6c61 100644 --- a/cetlvast/suites/unittest/test_rtti.cpp +++ b/cetlvast/suites/unittest/test_rtti.cpp @@ -8,25 +8,23 @@ #include #include -#define CETL_RTTI(base, ...) \ -public: \ - static constexpr cetl::type_id _type_id_{__VA_ARGS__}; \ - \ -protected: \ - cetl::type_id _get_type_id_() const noexcept override \ - { \ - return _type_id_; \ - } \ - void* _cast_(const cetl::type_id& id) noexcept override \ - { \ - return (id == _type_id_) ? static_cast(this) : base::_cast_(id); \ - } \ - const void* _cast_(const cetl::type_id& id) const noexcept override \ - { \ - return (id == _type_id_) ? static_cast(this) : base::_cast_(id); \ - } \ - \ -private: +#define CETL_RTTI(base, ...) \ + static constexpr cetl::type_id _get_static_type_id_() noexcept \ + { \ + return cetl::type_id{__VA_ARGS__}; \ + } \ + cetl::type_id _get_polymorphic_type_id_() const noexcept override \ + { \ + return _get_static_type_id_(); \ + } \ + void* _cast_(const cetl::type_id& id)& noexcept override \ + { \ + return (id == _get_static_type_id_()) ? static_cast(this) : base::_cast_(id); \ + } \ + const void* _cast_(const cetl::type_id& id) const& noexcept override \ + { \ + return (id == _get_static_type_id_()) ? static_cast(this) : base::_cast_(id); \ + } struct A : cetl::rtti { @@ -49,14 +47,120 @@ struct C : B struct D final { - static constexpr cetl::type_id _type_id_{ - {0x3, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}}; - const char value = 'd'; + static constexpr cetl::type_id _get_static_type_id_() noexcept + { + return {0x3, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}; + } }; TEST(test_rtti, basic) { + EXPECT_EQ(cetl::get_type_id(A{}), A::_get_static_type_id_()); + EXPECT_EQ(cetl::get_type_id(B{}), B::_get_static_type_id_()); + EXPECT_EQ(cetl::get_type_id(C{}), C::_get_static_type_id_()); + EXPECT_EQ(cetl::get_type_id(D{}), D::_get_static_type_id_()); + + EXPECT_EQ(nullptr, cetl::rtti_cast(static_cast(nullptr))); + EXPECT_EQ(nullptr, cetl::rtti_cast(static_cast(nullptr))); + EXPECT_EQ(nullptr, cetl::rtti_cast(static_cast(nullptr))); + EXPECT_EQ(nullptr, cetl::rtti_cast(static_cast(nullptr))); + A a; EXPECT_EQ(cetl::get_type_id(a), cetl::type_id({0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF})); + EXPECT_EQ(cetl::get_type_id(a), a._get_static_type_id_()); + EXPECT_EQ('a', cetl::rtti_cast(&a)->value); + EXPECT_EQ('a', cetl::rtti_cast(static_cast(&a))->value); + EXPECT_EQ(&a, cetl::rtti_cast(&a)); + EXPECT_EQ(&a, cetl::rtti_cast(static_cast(&a))); + EXPECT_EQ(nullptr, cetl::rtti_cast(&a)); + EXPECT_EQ(nullptr, cetl::rtti_cast(static_cast(&a))); +} + +TEST(test_rtti, polymorphism) +{ + using cetl::rtti_cast; + using cetl::is_instance_of; + + B b; + C c; + + EXPECT_EQ(cetl::get_type_id(b), + cetl::type_id({0x1, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF})); + EXPECT_EQ(cetl::get_type_id(c), + cetl::type_id({0x2, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF})); + EXPECT_EQ(cetl::get_type_id(b), b._get_static_type_id_()); + EXPECT_EQ(cetl::get_type_id(c), c._get_static_type_id_()); + + // identity, b to b + EXPECT_EQ('b', rtti_cast(&b)->value); + EXPECT_EQ('b', rtti_cast(static_cast(&b))->value); + EXPECT_EQ(&b, rtti_cast(&b)); + EXPECT_EQ(&b, rtti_cast(static_cast(&b))); + + // identity, c to c + EXPECT_EQ('c', rtti_cast(&c)->value); + EXPECT_EQ('c', rtti_cast(static_cast(&c))->value); + EXPECT_EQ(&c, rtti_cast(&c)); + EXPECT_EQ(&c, rtti_cast(static_cast(&c))); + + // up-conversion, b to a + EXPECT_EQ('a', rtti_cast(&b)->value); + EXPECT_EQ('a', rtti_cast(static_cast(&b))->value); + EXPECT_EQ(&b, rtti_cast(&b)); + EXPECT_EQ(&b, rtti_cast(static_cast(&b))); + + // up-conversion, c to b + EXPECT_EQ('b', rtti_cast(&c)->value); + EXPECT_EQ('b', rtti_cast(static_cast(&c))->value); + EXPECT_EQ(&c, rtti_cast(&c)); + EXPECT_EQ(&c, rtti_cast(static_cast(&c))); + + // up-conversion, c to a + EXPECT_EQ('a', rtti_cast(&c)->value); + EXPECT_EQ('a', rtti_cast(static_cast(&c))->value); + EXPECT_EQ(&c, rtti_cast(&c)); + EXPECT_EQ(&c, rtti_cast(static_cast(&c))); + + A& a_b = b; + A& a_c = c; + + // down-conversion, a to b + EXPECT_EQ('b', rtti_cast(&a_b)->value); + EXPECT_EQ('b', rtti_cast(static_cast(&a_b))->value); + EXPECT_EQ(&b, rtti_cast(&a_b)); + EXPECT_EQ(&b, rtti_cast(static_cast(&a_b))); + + // down-conversion, a to c + EXPECT_EQ('c', rtti_cast(&a_c)->value); + EXPECT_EQ('c', rtti_cast(static_cast(&a_c))->value); + EXPECT_EQ(&c, rtti_cast(&a_c)); + EXPECT_EQ(&c, rtti_cast(static_cast(&a_c))); + + // illegal down-conversion, b to c + EXPECT_EQ(nullptr, rtti_cast(&a_b)); + EXPECT_EQ(nullptr, rtti_cast(static_cast(&a_b))); + EXPECT_EQ(nullptr, rtti_cast(&b)); + EXPECT_EQ(nullptr, rtti_cast(static_cast(&b))); + + // is_instance_of + EXPECT_TRUE(is_instance_of(b)); + EXPECT_TRUE(is_instance_of(b)); + EXPECT_FALSE(is_instance_of(b)); + EXPECT_FALSE(is_instance_of(b)); + + EXPECT_TRUE(is_instance_of(c)); + EXPECT_TRUE(is_instance_of(c)); + EXPECT_TRUE(is_instance_of(c)); + EXPECT_FALSE(is_instance_of(c)); + + EXPECT_TRUE(is_instance_of(a_b)); + EXPECT_TRUE(is_instance_of(a_b)); + EXPECT_FALSE(is_instance_of(a_b)); + EXPECT_FALSE(is_instance_of(a_b)); + + EXPECT_TRUE(is_instance_of(a_c)); + EXPECT_TRUE(is_instance_of(a_c)); + EXPECT_TRUE(is_instance_of(a_c)); + EXPECT_FALSE(is_instance_of(a_c)); } diff --git a/include/cetl/rtti.hpp b/include/cetl/rtti.hpp index 0c0ce140..abd627b0 100644 --- a/include/cetl/rtti.hpp +++ b/include/cetl/rtti.hpp @@ -1,16 +1,7 @@ /// @file -/// /// An alternative implementation of simple runtime type information (RTTI) capability designed for high-integrity /// real-time systems, where the use of the standard C++ RTTI is discouraged. /// -/// Unlike the standard C++ RTTI, this implementation allows the user to manually specify the type ID per type -/// in the form of a 16-byte UUID (GUID), query runtime type information in constant time, -/// and perform safe dynamic type conversion in constant time (like \c dynamic_cast). -/// -/// In order to support this RTTI capability, a type must opt in by at least defining a public -/// static constexpr member named \c _type_id_ of type \ref cetl::type_id; if the type is polymorphic, -/// it should also implement the \ref cetl::rtti interface (more on this in the documentation of said interface). -/// /// @copyright /// Copyright (C) OpenCyphal Development Team /// Copyright Amazon.com Inc. or its affiliates. @@ -19,7 +10,6 @@ #ifndef CETL_RTTI_HPP_INCLUDED #define CETL_RTTI_HPP_INCLUDED -#include // This could be optionally replaced with cetlpf.hpp. #include #include @@ -28,63 +18,74 @@ namespace cetl { +/// This many bytes are used to represent a type ID. This is enough to hold a standard UUID (GUID). constexpr std::size_t type_id_size = 16; /// A 16-byte UUID (GUID) that uniquely identifies a type. /// The user is responsible for ensuring that each type that has opted into this RTTI capability has a unique type ID -/// exposed via a static constexpr member named \c _type_id_ of this type. +/// exposed via a public method static constexpr cetl::type_id _get_static_type_id_() noexcept. using type_id = std::array; -/// A null option is used to represent a type that has not opted into this RTTI capability (undefined type ID). -using maybe_type_id = pf17::optional; - namespace detail { template -auto has_type_id_impl(int) -> decltype(std::declval&>()._type_id_, std::true_type{}); +auto has_type_id_impl(int) -> decltype(std::declval&>()._get_static_type_id_(), std::true_type{}); template std::false_type has_type_id_impl(...); } // namespace detail -/// True iff \c T has a public field named \c _type_id_ of type \ref cetl::type_id; -/// i.e., \c T supports the RTTI capability. +/// True iff \c T has a public static method \c _get_static_type_id_(). template constexpr bool has_type_id = decltype(detail::has_type_id_impl>(0))::value; -/// A polymorphic interface that allows the user to query runtime type information and perform safe dynamic type -/// conversion in constant time. +/// An alternative implementation of simple runtime type information (RTTI) capability designed for high-integrity +/// real-time systems, where the use of the standard C++ RTTI is discouraged. +/// +/// Unlike the standard C++ RTTI, this implementation allows/requires the user to manually specify the type ID per type +/// in the form of a 16-byte UUID (GUID), query runtime type information in constant time, +/// and perform safe dynamic type down-conversion in constant time (similar to \c dynamic_cast). +/// The limitations are that a type has to opt into this RTTI capability explicitly and that CV-qualifiers +/// are not considered in the type comparison. /// -/// In order to support this RTTI implementation, a type must opt in by at least defining a public -/// static constexpr member named \c _type_id_ of type \ref cetl::type_id; if the type is polymorphic, -/// it should also implement this interface. +/// In order to support this RTTI capability, a type must opt in by at least defining a public method +/// static constexpr cetl::type_id _get_static_type_id_() noexcept; +/// such types satisfy \ref cetl::has_type_id. +/// If the type is polymorphic, it should also implement this interface. /// -/// The members of this type are named with surrounding underscores to avoid name clashes with user-defined members, +/// The static \c _get_static_type_id_() method returns the type ID of the type it is defined on. +/// It could be a static member variable as well, but in C++14 dealing with static members +/// of non-literal types is not as straightforward as in C++17, so we use a static method instead. +/// The virtual \ref rtti::_get_polymorphic_type_id_() method forwards the call to the static +/// \c _get_static_type_id_() method of the actual type of the object; +/// see the method documentation for details. +/// +/// RTTI-related members are named with surrounding underscores to avoid name clashes with user-defined members, /// including the members of DSDL types (where identifiers surrounded with underscores are reserved). class rtti { friend type_id get_type_id(const rtti& obj) noexcept; template ::value && has_type_id

, int>> - friend T rtti_cast(class rtti*) noexcept; + friend T rtti_cast(rtti*) noexcept; template ::value && has_type_id

, int>> - friend const P* rtti_cast(const class rtti*) noexcept; + friend const P* rtti_cast(const rtti*) noexcept; + + friend bool is_instance_of(const rtti& obj, const type_id& id) noexcept; protected: - /// The method body should be implemented as follows, - /// where \c _type_id_ is a static constexpr member of type \ref cetl::type_id: + /// The method body should simply forward the result of this type's static \c _get_static_type_id_ method: /// /// @code - /// return _type_id_; + /// return _get_static_type_id_(); /// @endcode /// - /// This method should not be invoked directly; instead, use the \ref cetl::get_type_id function. - CETL_NODISCARD virtual type_id _get_type_id_() const noexcept = 0; + /// This method should not be invoked directly; instead, use \ref cetl::get_type_id. + CETL_NODISCARD virtual type_id _get_polymorphic_type_id_() const noexcept = 0; - /// The method body should be implemented as follows, - /// where \c _type_id_ is a static constexpr member of type \ref cetl::type_id: + /// The method body should be implemented as follows (add \c const in the const overload): /// /// @code - /// return (id == _type_id_) ? this : base::_cast_(id); + /// return (id == _get_static_type_id_()) ? static_cast(this) : base::_cast_(id); /// @endcode /// /// Where \c base is the base class of the current class that implements this method. @@ -93,14 +94,14 @@ class rtti /// simply returns a null pointer, indicating that the search has returned no matches and a /// safe dynamic type conversion is not possible. /// - /// This method should not be invoked directly; instead, use the \ref cetl::rtti_cast function. + /// This method should not be invoked directly; instead, use \ref cetl::rtti_cast. /// @{ - CETL_NODISCARD virtual void* _cast_(const type_id& id) noexcept + CETL_NODISCARD virtual void* _cast_(const type_id& id) & noexcept { (void) id; return nullptr; } - CETL_NODISCARD virtual const void* _cast_(const type_id& id) const noexcept + CETL_NODISCARD virtual const void* _cast_(const type_id& id) const& noexcept { (void) id; return nullptr; @@ -115,36 +116,32 @@ class rtti rtti& operator=(rtti&&) = default; }; +/// Returns the type ID of the given type. +/// This function is provided for regularity; it simply forwards the call to \c T::_get_static_type_id_(). +/// The type shall satisfy \ref cetl::has_type_id. +template +CETL_NODISCARD constexpr type_id get_type_id() noexcept +{ + return std::decay_t::_get_static_type_id_(); +} /// Returns the type ID of the given object. /// This overload is for objects that implement the \ref cetl::rtti interface; it simply forwards the result of the -/// \ref cetl::rtti::_get_type_id_ method. -/// -/// Generic code is recommended to convert the result of this function into \ref maybe_type_id. +/// \ref cetl::rtti::_get_polymorphic_type_id_() method. CETL_NODISCARD inline type_id get_type_id(const rtti& obj) noexcept { - return obj._get_type_id_(); + return obj._get_polymorphic_type_id_(); } /// Returns the type ID of the given object. -/// This overload is selected for objects that do not implement the \ref cetl::rtti interface but provide a static -/// constexpr member named \c _type_id_ of type \ref cetl::type_id. -/// -/// Generic code is recommended to convert the result of this function into \ref maybe_type_id. +/// This overload is selected for objects that do not implement the \ref cetl::rtti interface but satisfy +/// \ref cetl::has_type_id. template , int> = 0> -CETL_NODISCARD constexpr type_id get_type_id(const T& obj) noexcept -{ - return obj._type_id_; -} -/// Returns the type ID of the given object. This is a fallback overload for objects that are not RTTI-capable; -/// it always returns an empty option. -template , int> = 0> -CETL_NODISCARD constexpr maybe_type_id get_type_id(const T&) noexcept +CETL_NODISCARD constexpr type_id get_type_id(const T&) noexcept { - return {}; + return get_type_id(); } /// Performs a safe dynamic type conversion in constant time by invoking \ref cetl::rtti::_cast_. -/// T shall be a pointer of a type that contains a public static constexpr member named \c _type_id_ of type -/// \ref cetl::type_id. +/// T shall satisfy \ref cetl::has_type_id. /// Returns nullptr if a safe dynamic type conversion to T is not possible. /// @{ template ::value && has_type_id

, int> = 0> CETL_NODISCARD T rtti_cast(rtti* const obj) noexcept { - return (obj == nullptr) ? nullptr : static_cast(obj->_cast_(P::_type_id_)); + return (obj == nullptr) ? nullptr : static_cast(obj->_cast_(get_type_id

())); } template , std::enable_if_t::value && has_type_id

, int> = 0> CETL_NODISCARD const P* rtti_cast(const rtti* const obj) noexcept { - return (obj == nullptr) ? nullptr : static_cast(obj->_cast_(P::_type_id_)); + return (obj == nullptr) ? nullptr : static_cast(obj->_cast_(get_type_id

())); } /// @} +/// Indicates whether the given polymorphic object is an instance of the type with the given type ID. +/// For example, given a polymorphic type hierarchy A<-B<-C, +/// \c is_instance_of(C{}, get_type_id()) is true for \c X in \c {A,B,C}. +CETL_NODISCARD inline bool is_instance_of(const rtti& obj, const type_id& id) noexcept +{ + return nullptr != obj._cast_(id); +} +/// Indicates whether the given polymorphic object is an instance of the given type. +/// T shall satisfy \ref cetl::has_type_id. +/// Refer to the non-template overload for more details. +template +CETL_NODISCARD bool is_instance_of(const rtti& obj) noexcept +{ + return is_instance_of(obj, get_type_id()); +} + } // namespace cetl #endif // CETL_RTTI_HPP_INCLUDED