diff --git a/cetlvast/suites/unittest/test_unbounded_variant.cpp b/cetlvast/suites/unittest/test_unbounded_variant.cpp index 0f672b1..36843e3 100644 --- a/cetlvast/suites/unittest/test_unbounded_variant.cpp +++ b/cetlvast/suites/unittest/test_unbounded_variant.cpp @@ -19,6 +19,44 @@ // NOLINTBEGIN(*-use-after-move) +namespace cetl +{ + +template <> +constexpr type_id type_id_value = {1}; + +template <> +constexpr type_id type_id_value = {2}; + +template <> +constexpr type_id type_id_value = {3}; + +template <> +constexpr type_id type_id_value = {4}; + +template <> +constexpr type_id type_id_value = {5}; + +template <> +constexpr type_id type_id_value = {6}; + +template <> +constexpr type_id type_id_value = {7}; + +template <> +constexpr type_id type_id_value> = {8}; + +template <> +constexpr type_id type_id_value> = {9}; + +template <> +constexpr type_id type_id_value = {10}; + +template <> +constexpr type_id type_id_value> = {11}; + +} // namespace cetl + namespace { @@ -28,6 +66,7 @@ using cetl::get_if; using cetl::make_unbounded_variant; using cetl::type_id; using cetl::type_id_type; +using cetl::type_id_invalid; using cetl::rtti_helper; using testing::_; @@ -94,7 +133,7 @@ struct side_effect_stats } }; -struct MyBase : rtti_helper> +struct MyBase : rtti_helper> { char payload_; int value_ = 0; @@ -186,7 +225,7 @@ struct MyCopyableOnly final : MyBase static constexpr type_id _get_type_id_() noexcept { - return {0x0, 0b01}; + return {0x1, 0b01}; } CETL_NODISCARD void* _cast_(const type_id& id) & noexcept override @@ -225,7 +264,7 @@ struct MyMovableOnly final : MyBase static constexpr type_id _get_type_id_() noexcept { - return {0x0, 0b10}; + return {0x1, 0b10}; } CETL_NODISCARD void* _cast_(const type_id& id) & noexcept override @@ -263,7 +302,7 @@ struct MyCopyableAndMovable final : MyBase static constexpr type_id _get_type_id_() noexcept { - return {0x0, 0b11}; + return {0x1, 0b11}; } CETL_NODISCARD void* _cast_(const type_id& id) & noexcept override @@ -382,6 +421,9 @@ TEST_F(TestPmrUnboundedVariant, cppref_example) TEST_F(TestPmrUnboundedVariant, ctor_1_default) { + EXPECT_THAT(unbounded_variant<1>{}.type_size(), 0UL); + EXPECT_THAT(unbounded_variant<1>{}.type_id(), type_id_invalid); + EXPECT_FALSE((unbounded_variant<1>{}.has_value())); EXPECT_FALSE((unbounded_variant<1, false>{}.has_value())); EXPECT_FALSE((unbounded_variant<1, false, true>{}.has_value())); @@ -397,6 +439,9 @@ TEST_F(TestPmrUnboundedVariant, ctor_1_default) TEST_F(TestPmrUnboundedVariant, ctor_1_default_pmr) { + EXPECT_THAT((unbounded_variant<0, true, true, 8, pmr>{get_mr()}.type_size()), 0UL); + EXPECT_THAT((unbounded_variant<0, true, true, 8, pmr>{get_mr()}.type_id()), type_id_invalid); + EXPECT_FALSE((unbounded_variant<0, false, false, 8, pmr>{get_mr()}.has_value())); EXPECT_FALSE((unbounded_variant<0, false, true, 8, pmr>{get_mr()}.has_value())); EXPECT_FALSE((unbounded_variant<0, true, false, 8, pmr>{get_mr()}.has_value())); @@ -420,12 +465,22 @@ TEST_F(TestPmrUnboundedVariant, ctor_2_copy) using ub_var = unbounded_variant; const ub_var src{42}; - ub_var dst{src}; + EXPECT_THAT(src.type_size(), sizeof(int)); + EXPECT_THAT(src.type_id(), cetl::type_id_value); + + ub_var dst{src}; + EXPECT_THAT(src.type_size(), sizeof(int)); + EXPECT_THAT(src.type_id(), cetl::type_id_value); + EXPECT_THAT(dst.type_size(), sizeof(int)); + EXPECT_THAT(dst.type_id(), cetl::type_id_value); EXPECT_THAT(get(src), 42); EXPECT_THAT(get(dst), 42); const ub_var empty{}; + EXPECT_THAT(empty.type_size(), 0); + EXPECT_THAT(empty.type_id(), type_id_invalid); + ub_var dst2{empty}; EXPECT_THAT(dst2.has_value(), false); dst2 = {}; @@ -1011,6 +1066,8 @@ TEST_F(TestPmrUnboundedVariant, get_if_polymorphic) auto side_effects = stats.make_side_effect_fn(); ub_var test_ubv = MyCopyableAndMovable{'Y', side_effects}; + EXPECT_THAT(test_ubv.type_size(), sizeof(MyCopyableAndMovable)); + EXPECT_THAT(test_ubv.type_id(), cetl::type_id_value); auto& test_base1 = get(test_ubv); EXPECT_THAT(test_base1.payload_, 'Y'); @@ -1020,6 +1077,8 @@ TEST_F(TestPmrUnboundedVariant, get_if_polymorphic) EXPECT_THAT(get_if(&test_ubv), IsNull()); test_ubv = MyBase{'X', side_effects}; + EXPECT_THAT(test_ubv.type_size(), sizeof(MyBase)); + EXPECT_THAT(test_ubv.type_id(), cetl::type_id_value); auto& test_base2 = get(test_ubv); EXPECT_THAT(test_base2.payload_, 'X'); @@ -1166,6 +1225,8 @@ TEST_F(TestPmrUnboundedVariant, emplace_1_ctor_exception) EXPECT_THAT(t.has_value(), false); EXPECT_THAT(t.valueless_by_exception(), true); + EXPECT_THAT(t.type_size(), 0); + EXPECT_THAT(t.type_id(), type_id_invalid); EXPECT_THAT(stats.constructs, 1); EXPECT_THAT(stats.destructs, 0); t.reset(); @@ -1402,6 +1463,8 @@ TEST_F(TestPmrUnboundedVariant, pmr_with_footprint_move_value_when_out_of_memory #endif EXPECT_THAT(dst.has_value(), false); EXPECT_THAT(dst.valueless_by_exception(), true); + EXPECT_THAT(dst.type_size(), 0); + EXPECT_THAT(dst.type_id(), type_id_invalid); EXPECT_THAT(stats.ops, "@"); } EXPECT_THAT(stats.constructs, stats.destructs); @@ -1725,45 +1788,13 @@ TEST_F(TestPmrUnboundedVariant, pmr_use_mock_as_custom_mr_type) namespace cetl { -template <> -constexpr type_id type_id_value = {1}; - -template <> -constexpr type_id type_id_value = {2}; - -template <> -constexpr type_id type_id_value = {3}; - -template <> -constexpr type_id type_id_value = {4}; - -template <> -constexpr type_id type_id_value = {5}; - -template <> -constexpr type_id type_id_value = {6}; - -template <> -constexpr type_id type_id_value = {7}; - template <> constexpr type_id type_id_value> = {0xB3, 0xB8, 0x4E, 0xC1, 0x1F, 0xE4, 0x49, 0x35, 0x9E, 0xC9, 0x1A, 0x77, 0x7B, 0x82, 0x53, 0x25}; template <> -constexpr type_id type_id_value> = {8}; - -template <> -constexpr type_id type_id_value> = {9}; - -template <> -constexpr type_id type_id_value = {10}; - -template <> -constexpr type_id type_id_value = {11}; - -template <> -constexpr type_id type_id_value> = {12}; +constexpr type_id type_id_value = + {0xD5, 0x62, 0x39, 0x66, 0x90, 0x8B, 0x4F, 0x56, 0x8F, 0x2A, 0x2F, 0x4F, 0xDF, 0x3F, 0x31, 0x5B}; } // namespace cetl diff --git a/include/cetl/rtti.hpp b/include/cetl/rtti.hpp index 9a35578..00598fa 100644 --- a/include/cetl/rtti.hpp +++ b/include/cetl/rtti.hpp @@ -35,6 +35,7 @@ using type_id = std::array; /// 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. +/// Please don't use empty or all zeros bytes, as it will be treated as the invalid type ID (see \ref type_id_invalid). template using type_id_type = std::integer_sequence; @@ -81,6 +82,15 @@ constexpr type_id type_id_type_value() noexcept template constexpr type_id type_id_value = T::_get_type_id_(); +/// The type ID value specializations for `void` type. +/// +/// In use for the `type_id_invalid` constant, which f.e. is in use at +/// `unbounded_variant::type_id()` method to indicate that the variant is valueless. +/// +template <> +constexpr type_id type_id_value{}; +constexpr type_id type_id_invalid{type_id_value}; + /// 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. /// diff --git a/include/cetl/unbounded_variant.hpp b/include/cetl/unbounded_variant.hpp index cdc47f7..66ab5ac 100644 --- a/include/cetl/unbounded_variant.hpp +++ b/include/cetl/unbounded_variant.hpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace cetl { @@ -542,7 +543,7 @@ struct base_access : base_storage base::template check_footprint(); CETL_DEBUG_ASSERT(nullptr == value_destroyer_, "Expected to be empty before making handlers."); - CETL_DEBUG_ASSERT(nullptr == value_converter_, ""); + CETL_DEBUG_ASSERT(nullptr == value_mut_converter_, ""); CETL_DEBUG_ASSERT(nullptr == value_const_converter_, ""); value_destroyer_ = [](void* const storage) { @@ -556,27 +557,55 @@ struct base_access : base_storage template , int> = 0> void make_converters() noexcept { - value_const_converter_ = [](const void* const storage, const type_id& id) { - const auto ptr = static_cast(storage); - return ptr->_cast_(id); + value_const_converter_ = [](const void* const storage, const cetl::type_id& dst_type_id) { + CETL_DEBUG_ASSERT(nullptr != storage, ""); + const auto ptr = static_cast(storage); + const void* const dst_ptr = ptr->_cast_(dst_type_id); + return std::make_pair(dst_ptr, cetl::type_id_value); }; - value_converter_ = [](void* const storage, const type_id& id) { - auto ptr = static_cast(storage); - return ptr->_cast_(id); + value_mut_converter_ = [](void* const storage, const cetl::type_id& dst_type_id) { + CETL_DEBUG_ASSERT(nullptr != storage, ""); + const auto ptr = static_cast(storage); + return ptr->_cast_(dst_type_id); }; } template , int> = 0> void make_converters() noexcept { - value_const_converter_ = [](const void* const storage, const type_id& id) { - return (id == type_id_value) ? storage : nullptr; + value_const_converter_ = [](const void* const storage, const cetl::type_id& dst_type_id) { + CETL_DEBUG_ASSERT(nullptr != storage, ""); + const void* const dst_ptr = (dst_type_id == cetl::type_id_value) ? storage : nullptr; + return std::make_pair(dst_ptr, cetl::type_id_value); }; - value_converter_ = [](void* const storage, const type_id& id) { - return (id == type_id_value) ? storage : nullptr; + value_mut_converter_ = [](void* const storage, const cetl::type_id& dst_type_id) { + CETL_DEBUG_ASSERT(nullptr != storage, ""); + return (dst_type_id == cetl::type_id_value) ? storage : nullptr; }; } + /// \brief Returns the unique identifier of the actual type of the stored value. + /// `cetl::type_id_invalid` if storage is empty. + /// + CETL_NODISCARD cetl::type_id type_id() const noexcept + { + if (!has_value()) + { + return cetl::type_id_invalid; + } + CETL_DEBUG_ASSERT(nullptr != value_const_converter_, "Non-empty storage is expected to have value converter."); + + return value_const_converter_(base::get_raw_storage(), {}).second; + } + + /// \brief Returns the size of the stored value in bytes. + /// Zero if storage is empty. + /// + CETL_NODISCARD std::size_t type_size() const noexcept + { + return has_value() ? base::get_value_size() : 0UL; + } + template CETL_NODISCARD void* get_ptr() noexcept { @@ -588,7 +617,7 @@ struct base_access : base_storage } CETL_DEBUG_ASSERT(nullptr != value_const_converter_, "Non-empty storage is expected to have value converter."); - return value_converter_(base::get_raw_storage(), type_id_value); + return value_mut_converter_(base::get_raw_storage(), cetl::type_id_value); } template @@ -602,20 +631,20 @@ struct base_access : base_storage } CETL_DEBUG_ASSERT(nullptr != value_const_converter_, "Non-empty storage is expected to have value converter."); - return value_const_converter_(base::get_raw_storage(), type_id_value); + return value_const_converter_(base::get_raw_storage(), cetl::type_id_value).first; } void copy_handlers_from(const base_access& src) noexcept { value_destroyer_ = src.value_destroyer_; - value_converter_ = src.value_converter_; + value_mut_converter_ = src.value_mut_converter_; value_const_converter_ = src.value_const_converter_; } void move_handlers_from(base_access& src) noexcept { value_destroyer_ = src.value_destroyer_; - value_converter_ = src.value_converter_; + value_mut_converter_ = src.value_mut_converter_; value_const_converter_ = src.value_const_converter_; src.reset(); @@ -632,7 +661,7 @@ struct base_access : base_storage } value_destroyer_ = nullptr; - value_converter_ = nullptr; + value_mut_converter_ = nullptr; value_const_converter_ = nullptr; base::reset(); @@ -644,10 +673,21 @@ struct base_access : base_storage // Holds type-erased value destroyer. `nullptr` if storage has no value stored. void (*value_destroyer_)(void* self) = nullptr; - // Holds type-erased value converters (const and non-const). `nullptr` if storage has no value stored. + // Holds type-erased value converter. `nullptr` if storage has no value stored. + // This function does polymorphic casting, and returns converted raw pointer to the destination mutable value + // type (according to `dst_type_id`), otherwise `nullptr` if conversion is impossible (or `self` is `nullptr`). + // + void* (*value_mut_converter_)(void* self, const cetl::type_id& dst_type_id) = nullptr; + + // Holds type-erased const value converter. `nullptr` if storage has no value stored. + // This function does polymorphic casting, and returns: + // - Converted raw pointer to the destination const value type (according to `dst_type_id`), + // otherwise `nullptr` if conversion is impossible (or `self` is `nullptr`); + // - Unique identifier of the actual type of the stored value, + // otherwise `type_id_invalid` (all zeros) if storage is empty. // - void* (*value_converter_)(void* self, const type_id& id) = nullptr; - const void* (*value_const_converter_)(const void* self, const type_id& id) = nullptr; + using ValueConstPtrAndTypeId = std::pair; + ValueConstPtrAndTypeId (*value_const_converter_)(const void* self, const cetl::type_id& dst_type_id) = nullptr; }; // base_access @@ -1001,6 +1041,8 @@ class unbounded_variant : detail::base_move