From 315e6b6815799c535bfdcb758bae83dd74fe7b71 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Tue, 20 Feb 2024 22:41:31 +0200 Subject: [PATCH] Competing proposal: do not use macro, use a helper base instead #verification #docs #sonar --- cetlvast/suites/unittest/test_rtti.cpp | 361 +++++++++++++++++-------- include/cetl/rtti.hpp | 228 ++++++++++++---- 2 files changed, 421 insertions(+), 168 deletions(-) diff --git a/cetlvast/suites/unittest/test_rtti.cpp b/cetlvast/suites/unittest/test_rtti.cpp index 995be558..9c958dd3 100644 --- a/cetlvast/suites/unittest/test_rtti.cpp +++ b/cetlvast/suites/unittest/test_rtti.cpp @@ -8,166 +8,293 @@ #include #include -/// An optional helper that can be used to implement CETL RTTI support with minimal boilerplate. -/// Use it in the public section of the class definition. -/// -/// The first argument is the base class of the class that is being defined, which can be \c cetl::rtti. -/// -/// The following arguments are the 16 bytes of the type identifier exposed via \c _get_static_type_id_; -/// if less than 16 bytes are provided, the remaining bytes are zeroed. -#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); \ +/// A simple non-polymorphic type that supports CETL RTTI. +struct Static final +{ + static constexpr cetl::type_id _get_static_type_id_() noexcept + { + return {0x3, 0x0}; } +}; -struct A : cetl::rtti +/// A simple polymorphic inheritance hierarchy: A <- B <- C. +struct PolymorphA : cetl::rtti_helper> { - ~A() override = default; - const char value = 'a'; - CETL_RTTI(cetl::rtti, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF) + char value = 'a'; }; - -struct B : A +struct PolymorphB : cetl::rtti_helper, PolymorphA> { - const char value = 'b'; - CETL_RTTI(A, 0x1, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF) + char value = 'b'; }; - -struct C : B +struct PolymorphC : cetl::rtti_helper, PolymorphB> { - const char value = 'c'; - CETL_RTTI(B, 0x2, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF) + char value = 'c'; + const char& value_a() const + { + return PolymorphA::value; + } + char& value_b() + { + return PolymorphB::value; + } }; -struct D final +/// A diamond multi-inheritance hierarchy: +/// A +/// / ` +/// B C +/// ` / +/// D +struct MultiA : cetl::rtti_helper> { - static constexpr cetl::type_id _get_static_type_id_() noexcept + char value = 'a'; +}; +struct MultiB : cetl::rtti_helper, MultiA> +{ + char value = 'b'; + char& value_b_a() { - return {0x3, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}; + return MultiA::value; + } +}; +struct MultiC : cetl::rtti_helper, MultiA> +{ + char value = 'c'; + char& value_c_a() + { + return MultiA::value; + } +}; +struct MultiD : cetl::rtti_helper, MultiB, MultiC> +{ + char value = 'd'; + char& value_b() + { + return MultiB::value; + } + char& value_c() + { + return MultiC::value; } }; 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(PolymorphA{}), PolymorphA::_get_static_type_id_()); + EXPECT_EQ(cetl::get_type_id(PolymorphB{}), PolymorphB::_get_static_type_id_()); + EXPECT_EQ(cetl::get_type_id(PolymorphC{}), PolymorphC::_get_static_type_id_()); + EXPECT_EQ(cetl::get_type_id(Static{}), Static::_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))); +} + +TEST(test_rtti, basic_single_inheritance) +{ + PolymorphA a; + EXPECT_EQ(cetl::get_type_id(a), cetl::type_id({0x0, 0x1})); 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))); + 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) +TEST(test_rtti, basic_multi_inheritance) +{ + MultiD d; + EXPECT_EQ(cetl::get_type_id(d), cetl::type_id({0x3, 0x2})); + EXPECT_EQ(cetl::get_type_id(d), d._get_static_type_id_()); + + EXPECT_TRUE(cetl::is_instance_of(d)); + EXPECT_TRUE(cetl::is_instance_of(d)); + EXPECT_TRUE(cetl::is_instance_of(d)); + EXPECT_TRUE(cetl::is_instance_of(d)); + + EXPECT_TRUE(cetl::is_instance_of(static_cast(d))); + EXPECT_TRUE(cetl::is_instance_of(static_cast(d))); + EXPECT_TRUE(cetl::is_instance_of(static_cast(d))); + EXPECT_TRUE(cetl::is_instance_of(static_cast(d))); + + EXPECT_TRUE(cetl::is_instance_of(static_cast(d))); + EXPECT_TRUE(cetl::is_instance_of(static_cast(d))); + EXPECT_TRUE(cetl::is_instance_of(static_cast(d))); + EXPECT_TRUE(cetl::is_instance_of(static_cast(d))); + + EXPECT_TRUE(cetl::is_instance_of(static_cast(static_cast(d)))); + EXPECT_TRUE(cetl::is_instance_of(static_cast(static_cast(d)))); + EXPECT_TRUE(cetl::is_instance_of(static_cast(static_cast(d)))); + EXPECT_TRUE(cetl::is_instance_of(static_cast(static_cast(d)))); + + EXPECT_TRUE(cetl::is_instance_of(static_cast(static_cast(d)))); + EXPECT_TRUE(cetl::is_instance_of(static_cast(static_cast(d)))); + EXPECT_TRUE(cetl::is_instance_of(static_cast(static_cast(d)))); + EXPECT_TRUE(cetl::is_instance_of(static_cast(static_cast(d)))); + + MultiB b; + EXPECT_FALSE(cetl::is_instance_of(b)); + EXPECT_TRUE(cetl::is_instance_of(b)); + EXPECT_FALSE(cetl::is_instance_of(b)); + EXPECT_TRUE(cetl::is_instance_of(b)); +} + +TEST(test_rtti, single_inheritance) { using cetl::rtti_cast; using cetl::is_instance_of; - B b; - C c; + PolymorphB b; + PolymorphC 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), cetl::type_id({0x1, 0x1})); + EXPECT_EQ(cetl::get_type_id(c), cetl::type_id({0x2, 0x1})); EXPECT_EQ(cetl::get_type_id(b), b._get_static_type_id_()); EXPECT_EQ(cetl::get_type_id(c), c._get_static_type_id_()); + // check values + EXPECT_EQ('c', c.value); + EXPECT_EQ('a', c.value_a()); + EXPECT_EQ('b', c.value_b()); + static_cast(c).value = 'A'; + c.value_b() = 'B'; + EXPECT_EQ('A', c.value_a()); + EXPECT_EQ('B', static_cast(c).value); + // 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))); + 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))); + 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))); + 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))); + 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))); + 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; + PolymorphA& a_b = b; + PolymorphA& 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))); + 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))); + 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))); + 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)); + 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)); +} + +TEST(test_rtti, multi_inheritance) +{ + MultiD d; + + EXPECT_EQ(cetl::get_type_id(d), cetl::type_id({0x3, 0x2})); + EXPECT_EQ(cetl::get_type_id(d), d._get_static_type_id_()); + + // check values + EXPECT_EQ('d', d.value); + EXPECT_EQ('b', d.value_b()); + EXPECT_EQ('c', d.value_c()); + EXPECT_EQ('a', d.value_b_a()); + EXPECT_EQ('a', d.value_c_a()); + d.value_b() = 'B'; + d.value_c() = 'C'; + d.value_b_a() = 'p'; + d.value_c_a() = 'o'; + EXPECT_EQ('B', static_cast(d).value); + EXPECT_EQ('C', static_cast(d).value); + EXPECT_EQ('p', static_cast(static_cast(d)).value); + EXPECT_EQ('o', static_cast(static_cast(d)).value); + + // identity + EXPECT_EQ('d', cetl::rtti_cast(&d)->value); + EXPECT_EQ('d', cetl::rtti_cast(static_cast(&d))->value); + EXPECT_EQ(&d, cetl::rtti_cast(&d)); + EXPECT_EQ(&d, cetl::rtti_cast(static_cast(&d))); + + // up-conversion, d to b + EXPECT_EQ('B', cetl::rtti_cast(&d)->value); + EXPECT_EQ('B', cetl::rtti_cast(static_cast(&d))->value); + EXPECT_EQ(&d, cetl::rtti_cast(&d)); + EXPECT_EQ(&d, cetl::rtti_cast(static_cast(&d))); + + // up-conversion, d to c + EXPECT_EQ('C', cetl::rtti_cast(&d)->value); + EXPECT_EQ('C', cetl::rtti_cast(static_cast(&d))->value); + EXPECT_EQ(&d, cetl::rtti_cast(&d)); + EXPECT_EQ(&d, cetl::rtti_cast(static_cast(&d))); + + // up-conversion, d to a; the base ambiguity is resolved by the order of inheritance: A<-B<-D wins over A<-C<-D. + EXPECT_EQ('p', cetl::rtti_cast(&d)->value); + EXPECT_EQ('p', cetl::rtti_cast(static_cast(&d))->value); + EXPECT_EQ(static_cast(static_cast(&d)), cetl::rtti_cast(&d)); + + // down-conversion, b to d + EXPECT_EQ('d', cetl::rtti_cast(static_cast(&d))->value); + EXPECT_EQ(&d, cetl::rtti_cast(static_cast(&d))); + + // down-conversion, c to d + EXPECT_EQ('d', cetl::rtti_cast(static_cast(&d))->value); + EXPECT_EQ(&d, cetl::rtti_cast(static_cast(&d))); + + // down-conversion, a to d through b + EXPECT_EQ('d', cetl::rtti_cast(static_cast(static_cast(&d)))->value); + EXPECT_EQ(&d, cetl::rtti_cast(static_cast(static_cast(&d)))); + + // down-conversion, a to d through c + EXPECT_EQ('d', cetl::rtti_cast(static_cast(static_cast(&d)))->value); + EXPECT_EQ(&d, cetl::rtti_cast(static_cast(static_cast(&d)))); } diff --git a/include/cetl/rtti.hpp b/include/cetl/rtti.hpp index 2ad9cae9..a593c9d0 100644 --- a/include/cetl/rtti.hpp +++ b/include/cetl/rtti.hpp @@ -1,5 +1,5 @@ /// @file -/// An alternative implementation of simple runtime type information (RTTI) capability designed for high-integrity +/// A simple runtime type information (RTTI) support designed for high-integrity /// real-time systems, where the use of the standard C++ RTTI is discouraged. /// /// @copyright @@ -14,6 +14,7 @@ #include #include +#include #include namespace cetl @@ -26,55 +27,91 @@ constexpr std::size_t type_id_size = 16; /// exposed via a public method static constexpr cetl::type_id _get_static_type_id_() noexcept. using type_id = std::array; +/// This is used for representing the type ID of a type as a type, similar to \ref std::integral_constant. +/// The bytes of the UUID are given as a list of template parameters; there shall be at most 16 of them; +/// if any are missing, they are assumed to be 0. +/// For conversion to \ref type_id use \ref cetl::type_id_type_value. +template +using type_id_type = std::integer_sequence; + namespace detail { +// has_static_type_id_impl +template +auto has_static_type_id_impl(int) -> decltype(std::decay_t::_get_static_type_id_(), std::true_type{}); +template +std::false_type has_static_type_id_impl(...); + +// has_polymorphic_type_id_impl +template +auto has_polymorphic_type_id_impl(int) + -> decltype(std::declval&>()._get_polymorphic_type_id_(), std::true_type{}); +template +std::false_type has_polymorphic_type_id_impl(...); + +// has_cast_impl template -auto has_type_id_impl(int) -> decltype(std::declval&>()._get_static_type_id_(), std::true_type{}); +auto has_cast_impl(int) -> decltype(std::declval&>()._cast_(std::declval()), std::true_type{}); template -std::false_type has_type_id_impl(...); +std::false_type has_cast_impl(...); + +// type_id_type_value_impl +template +constexpr type_id type_id_type_value_impl(const type_id_type) noexcept +{ + return {{Bytes...}}; +} } // namespace detail /// True iff \c T has a public static method \c _get_static_type_id_(). -/// Keep in mind that this RTTI implementation ignores CV-qualifiers and references. template -constexpr bool has_type_id = decltype(detail::has_type_id_impl>(0))::value; +constexpr bool has_static_type_id = decltype(detail::has_static_type_id_impl>(0))::value; + +/// True iff \c T has a public method \c _get_polymorphic_type_id_(). +/// One could argue that instead of relying on this trait, we could just use `const rtti&` or +/// `std::is_base_of`. +/// This won't work in the presence of multiple inheritance because it makes implicit conversion +/// to the base class ambiguous. +template +constexpr bool has_polymorphic_type_id = decltype(detail::has_polymorphic_type_id_impl>(0))::value; + +/// True iff \c T has a public method \c _cast_(). +template +constexpr bool is_rtti_convertible = decltype(detail::has_cast_impl>(0))::value; + +/// A helper that converts \ref cetl::type_id_type to \ref cetl::type_id. +template +constexpr type_id type_id_type_value = detail::type_id_type_value_impl(TypeIDType{}); /// 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 requires the user to manually specify the type ID per type /// in the form of a 16-byte UUID (GUID). The capabilities include querying runtime type information in constant time -/// and performing safe dynamic type down-conversion (and up-conversion, similar to \c dynamic_cast) in constant time. -/// The limitations are that a type has to opt into this RTTI capability explicitly and that CV-qualifiers -/// and references are not considered in the type comparison; that is, T and const T& -/// are considered the same type. +/// and performing safe dynamic type down-conversion (and up-conversion, similar to \c dynamic_cast) in constant time, +/// including the case of multiple inheritance. +/// +/// The limitations are that a type has to opt into this RTTI capability explicitly (it doesn't work for all types) +/// and that CV-qualifiers and references are not considered in the type comparison; +/// that is, T and const T& are considered the same type. /// /// In order to support this RTTI capability, a type must at least define a public method /// static constexpr cetl::type_id _get_static_type_id_() noexcept that /// returns the type ID (i.e., the UUID) of the type it is defined on. -/// Types that provide said method satisfy \ref cetl::has_type_id. -/// If the type is polymorphic, aside from the above method it should also implement this interface; -/// for details on that please refer to the method documentation. +/// Types that provide said method satisfy \ref cetl::has_static_type_id. +/// If the type is polymorphic, it should publicly inherit from \ref cetl::rtti_helper, which will provide the +/// necessary implementation of the RTTI-related methods along with the aforementioned +/// \c _get_static_type_id_() method. /// /// 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). /// The user code should not invoke such underscored methods directly but instead use the free functions defined here. class rtti { - friend type_id get_type_id(const rtti& obj) noexcept; - - template - friend std::enable_if_t::value && has_type_id>, T> rtti_cast( - rtti*) noexcept; - template - friend std::enable_if_t::value && has_type_id>, - const std::remove_pointer_t*> - rtti_cast(const rtti*) noexcept; - - friend bool is_instance_of(const rtti& obj, const type_id& id) noexcept; - -protected: - /// The method body should simply forward the result of this type's static \c _get_static_type_id_ method: +public: + /// Implementations are adivsed to use \ref cetl::rtti_helper instead of implementing this manually. + /// If manual implementation is preferred, then the method body should simply forward the result of this + /// type's static \c _get_static_type_id_ method: /// /// @code /// return _get_static_type_id_(); @@ -83,17 +120,22 @@ class rtti /// 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 (add \c const in the const overload): + /// Implementations are adivsed to use \ref cetl::rtti_helper instead of implementing this manually. + /// If manual implementation is preferred, then the method body should be implemented as follows + /// (add \c const in the const overload): /// /// @code /// 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. + /// Where \c base is the base class of the current class that implements this method; + /// if there is more than one base available, all of those that implement this \ref cetl::rtti interface + /// should be checked in the same way one by one and the first match (non-nullptr result) should be returned. + /// /// The objective here is to check if the current type in the hierarchy is the one being sought; - /// if not, delegate the call to the base class; the topmost class is this \c rtti type that + /// if not, delegate the call to the base class(es); the topmost class is this \c rtti type that /// simply returns a null pointer, indicating that the search has returned no matches and a - /// safe dynamic type conversion is not possible. + /// safe dynamic type conversion is not possible (at least not along the current branch of the type tree). /// /// This method should not be invoked directly; instead, use \ref cetl::rtti_cast. /// @{ @@ -109,6 +151,7 @@ class rtti } /// @} +protected: rtti() = default; rtti(const rtti&) = default; rtti(rtti&&) = default; @@ -117,9 +160,82 @@ class rtti rtti& operator=(rtti&&) = default; }; +/// Non-polymorphic types that want to support RTTI should simply provide a \c _get_static_type_id_() +/// method that returns \ref cetl::type_id; there is no need for them to use this helper. +/// +/// Polymorphic types, on the other hand, are trickier because their runtime type may not be the same as their +/// static type; for this reason, they should publicly inherit from this helper class, +/// which will provide the necessary implementation of the RTTI-related methods +/// along with the aforementioned \c _get_static_type_id_ method. +/// +/// If a polymorphic type inherits from other classes that also implement the \ref cetl::rtti interface, +/// such inheritance should be done not directly but through this helper; that is, instead of inheriting +/// from \c A and \c B, the type should inherit from \c rtti_helper. +/// This is done to inform the helper about the type hierarchy, allowing it to perform an exhaustive search +/// for a matching conversion throughout the entire type hierarchy tree in the presence of multiple inheritance. +/// +/// Conversion to an ambiguous base class is not allowed in C++ (except for the case of virtual inheritance +/// +/// \tparam TypeIDType The type ID encoded via \ref cetl::type_id_type. +/// \tparam FirstBase An optional base class that implements the \ref cetl::rtti interface. +/// \tparam OtherBases Other base classes that implement the \ref cetl::rtti interface, if any. +template +struct rtti_helper : public FirstBase, public OtherBases... +{ + static constexpr type_id _get_static_type_id_() noexcept + { + return type_id_type_value; + } + type_id _get_polymorphic_type_id_() const noexcept override + { + return _get_static_type_id_(); + } + void* _cast_(const type_id& id) & noexcept override + { + return (id == _get_static_type_id_()) ? static_cast(this) : search(id); + } + const void* _cast_(const cetl::type_id& id) const& noexcept override + { + return (id == _get_static_type_id_()) ? static_cast(this) : search(id); + } + +private: + // Exhaustively search for a matching conversion throughout the entire type hierarchy tree. + // Template parameter pack expansion is not available in C++14 so we do it the hard way. + template + void* search(const type_id& id) noexcept + { + return T::_cast_(id); + } + template + void* search(const type_id& id) noexcept + { + if (void* const p = search(id)) + { + return p; + } + return search(id); + } + // Same but const. + template + const void* search(const type_id& id) const noexcept + { + return T::_cast_(id); + } + template + const void* search(const type_id& id) const noexcept + { + if (const void* const p = search(id)) + { + return p; + } + return search(id); + } +}; + /// 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. +/// The type shall satisfy \ref cetl::has_static_type_id. template CETL_NODISCARD constexpr type_id get_type_id() noexcept { @@ -128,35 +244,41 @@ CETL_NODISCARD constexpr type_id get_type_id() noexcept /// 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_polymorphic_type_id_() method. -CETL_NODISCARD inline type_id get_type_id(const rtti& obj) noexcept +template , int> = 0> +CETL_NODISCARD type_id get_type_id(const T& obj) noexcept { 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 satisfy -/// \ref cetl::has_type_id. -template , int> = 0> +/// \ref cetl::has_static_type_id. +template && !has_polymorphic_type_id, int> = 0> CETL_NODISCARD constexpr type_id get_type_id(const T&) noexcept { return get_type_id(); } /// Performs a safe dynamic type up-/down-conversion in constant time by invoking \ref cetl::rtti::_cast_. -/// \c T shall be a pointer and \c std::remove_pointer_t shall satisfy \ref cetl::has_type_id. +/// \c T shall be a pointer and \c std::remove_pointer_t shall satisfy \ref cetl::has_static_type_id. /// Returns \c nullptr if a safe dynamic type conversion to \c T is not possible. /// @{ -template -CETL_NODISCARD std::enable_if_t::value && has_type_id>, T> rtti_cast( - rtti* const obj) noexcept +template +CETL_NODISCARD std::enable_if_t && // + std::is_pointer::value && // + has_static_type_id>, + T> + rtti_cast(_from* const obj) noexcept { return (obj == nullptr) // ? nullptr : static_cast(obj->_cast_(get_type_id>())); } -template -CETL_NODISCARD std::enable_if_t::value && has_type_id>, +template +CETL_NODISCARD std::enable_if_t && // + std::is_pointer::value && // + has_static_type_id>, const std::remove_pointer_t*> - rtti_cast(const rtti* const obj) noexcept + rtti_cast(const _from* const obj) noexcept { return (obj == nullptr) // ? nullptr @@ -164,21 +286,25 @@ CETL_NODISCARD std::enable_if_t::value && has_type_idA<-B<-C, -/// \c is_instance_of(C{}, get_type_id()) is true for \c X in \c {A,B,C}; -/// while \c is_instance_of(A{}, get_type_id()) is only true for \c X=A. -CETL_NODISCARD inline bool is_instance_of(const rtti& obj, const type_id& id) noexcept +/// \c is_instance_of(C{}, get_type_id())==true for \c X in \c {A,B,C}; +/// while \c is_instance_of(A{}, get_type_id())==true only for \c X=A. +/// +/// Note that the type of the object argument is not `rtti&` but the actual type of the object, +/// because in the presence of multiple inheritance implicit conversion to the base class is ambiguous. +template +CETL_NODISCARD std::enable_if_t, bool> is_instance_of(const _u& 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 +/// Detects whether the given polymorphic object is an instance of the given type. +/// T shall satisfy \ref cetl::has_static_type_id. +/// Refer to the non-template overload for details. +template +CETL_NODISCARD std::enable_if_t, bool> is_instance_of(const _u& obj) noexcept { - return is_instance_of(obj, get_type_id()); + return is_instance_of(obj, get_type_id()); } } // namespace cetl