diff --git a/cetlvast/include/cetlvast/helpers_rtti.hpp b/cetlvast/include/cetlvast/helpers_rtti.hpp new file mode 100644 index 0000000..f34614a --- /dev/null +++ b/cetlvast/include/cetlvast/helpers_rtti.hpp @@ -0,0 +1,100 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef CETLVAST_HELPERS_RTTI_HPP +#define CETLVAST_HELPERS_RTTI_HPP + +#include "cetl/rtti.hpp" + +#include +#include +#include +#include +#include + +namespace cetl +{ + +// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers) + +// 6B02B2B9-610B-414E-9304-E7FC5BC0D061 +template <> +constexpr type_id type_id_getter() noexcept +{ + return {0x6B, 0x02, 0xB2, 0xB9, 0x61, 0x0B, 0x41, 0x4E, 0x93, 0x04, 0xE7, 0xFC, 0x5B, 0xC0, 0xD0, 0x61}; +} +// AA3F7C4D-0E44-43CB-AB4C-2AE19E646F91 +template <> +constexpr type_id type_id_getter() noexcept +{ + return {0xAA, 0x3F, 0x7C, 0x4D, 0x0E, 0x44, 0x43, 0xCB, 0xAB, 0x4C, 0x2A, 0xE1, 0x9E, 0x64, 0x6F, 0x91}; +} +// 42844900-45ED-41A0-AA63-D6A42B60B343 +template <> +constexpr type_id type_id_getter() noexcept +{ + return {0x42, 0x84, 0x49, 0x00, 0x45, 0xED, 0x41, 0xA0, 0xAA, 0x63, 0xD6, 0xA4, 0x2B, 0x60, 0xB3, 0x43}; +} +// 6B5BE490-194C-4E2E-B8DE-3BB15CC52777 +template <> +constexpr type_id type_id_getter() noexcept +{ + return {0x6B, 0x5B, 0xE4, 0x90, 0x19, 0x4C, 0x4E, 0x2E, 0xB8, 0xDE, 0x3B, 0xB1, 0x5C, 0xC5, 0x27, 0x77}; +} + +// 05855903-D323-41C3-8C58-691E035507D8 +template <> +constexpr type_id type_id_getter() noexcept +{ + return {0x05, 0x85, 0x59, 0x03, 0xD3, 0x23, 0x41, 0xC3, 0x8C, 0x58, 0x69, 0x1E, 0x03, 0x55, 0x07, 0xD8}; +} +// 6BC0579E-B665-480A-AFB0-45DB755A143E +template <> +constexpr type_id type_id_getter() noexcept +{ + return {0x6B, 0xC0, 0x57, 0x9E, 0xB6, 0x65, 0x48, 0x0A, 0xAF, 0xB0, 0x45, 0xDB, 0x75, 0x5A, 0x14, 0x3E}; +} +// 3C22EF31-63C0-4710-9AAE-966E89134C19 +template <> +constexpr type_id type_id_getter() noexcept +{ + return {0x3C, 0x22, 0xEF, 0x31, 0x63, 0xC0, 0x47, 0x10, 0x9A, 0xAE, 0x96, 0x6E, 0x89, 0x13, 0x4C, 0x19}; +} +// 89A2F7BC-5BEA-47BF-96C4-CFFA3A2DBBB2 +template <> +constexpr type_id type_id_getter() noexcept +{ + return {0x89, 0xA2, 0xF7, 0xBC, 0x5B, 0xEA, 0x47, 0xBF, 0x96, 0xC4, 0xCF, 0xFA, 0x3A, 0x2D, 0xBB, 0xB2}; +} +// A0672C3A-C6D2-4BF5-990A-1A4601264D60 +template <> +constexpr type_id type_id_getter() noexcept +{ + return {0xA0, 0x67, 0x2C, 0x3A, 0xC6, 0xD2, 0x4B, 0xF5, 0x99, 0x0A, 0x1A, 0x46, 0x01, 0x26, 0x4D, 0x60}; +} +// 473A0E53-86AB-4426-9F32-732D519F940D +template <> +constexpr type_id type_id_getter>() noexcept +{ + return {0x47, 0x3A, 0x0E, 0x53, 0x86, 0xAB, 0x44, 0x26, 0x9F, 0x32, 0x73, 0x2D, 0x51, 0x9F, 0x94, 0x0D}; +} +// D30E9194-8ECB-4831-9B31-F73C031DBFFB +template <> +constexpr type_id type_id_getter>() noexcept +{ + return {0xD3, 0x0E, 0x91, 0x94, 0x8E, 0xCB, 0x48, 0x31, 0x9B, 0x31, 0xF7, 0x3C, 0x03, 0x1D, 0xBF, 0xFB}; +} +// 63E796F8-AAFC-4E61-B545-99CE28B796FD +template <> +constexpr type_id type_id_getter>() noexcept +{ + return {0x63, 0xE7, 0x96, 0xF8, 0xAA, 0xFC, 0x4E, 0x61, 0xB5, 0x45, 0x99, 0xCE, 0x28, 0xB7, 0x96, 0xFD}; +} + +// NOLINTEND(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers) + +} // namespace cetl + +#endif // CETLVAST_HELPERS_RTTI_HPP diff --git a/cetlvast/suites/compile/CMakeLists.txt b/cetlvast/suites/compile/CMakeLists.txt index 115b4c3..acdbc7b 100644 --- a/cetlvast/suites/compile/CMakeLists.txt +++ b/cetlvast/suites/compile/CMakeLists.txt @@ -21,7 +21,7 @@ set(ALL_TESTS_RUN "") foreach(COMPILE_TEST ${COMPILE_TESTS}) define_compile_failure_test(TEST_SOURCE ${COMPILE_TEST} - EXTRA_TEST_LIBS cetl + EXTRA_TEST_LIBS cetl cetlvast OUT_TEST_BUILD_TARGET COMPILE_TEST_BUILD_TARGET OUT_TEST_PRECHECK_TARGET COMPILE_TEST_PRECHECK_TARGET) list(APPEND ALL_TESTS_BUILD "${COMPILE_TEST_BUILD_TARGET}") diff --git a/cetlvast/suites/compile/test_unbounded_variant_footprint_get_const.cpp b/cetlvast/suites/compile/test_unbounded_variant_footprint_get_const.cpp index 1e82ed0..fea4f07 100644 --- a/cetlvast/suites/compile/test_unbounded_variant_footprint_get_const.cpp +++ b/cetlvast/suites/compile/test_unbounded_variant_footprint_get_const.cpp @@ -8,19 +8,10 @@ /// #include "cetl/unbounded_variant.hpp" +#include "cetlvast/helpers_rtti.hpp" #include -namespace cetl -{ -template <> -constexpr type_id type_id_value{}; - -template <> -constexpr type_id type_id_value{}; - -} // namespace cetl - int main() { using ub_var = cetl::unbounded_variant; diff --git a/cetlvast/suites/compile/test_unbounded_variant_footprint_get_non_const.cpp b/cetlvast/suites/compile/test_unbounded_variant_footprint_get_non_const.cpp index c118861..339f44e 100644 --- a/cetlvast/suites/compile/test_unbounded_variant_footprint_get_non_const.cpp +++ b/cetlvast/suites/compile/test_unbounded_variant_footprint_get_non_const.cpp @@ -8,19 +8,10 @@ /// #include "cetl/unbounded_variant.hpp" +#include "cetlvast/helpers_rtti.hpp" #include -namespace cetl -{ -template <> -constexpr type_id type_id_value{}; - -template <> -constexpr type_id type_id_value{}; - -} // namespace cetl - int main() { using ub_var = cetl::unbounded_variant; diff --git a/cetlvast/suites/compile/test_unbounded_variant_footprint_set.cpp b/cetlvast/suites/compile/test_unbounded_variant_footprint_set.cpp index c966332..8acb5c6 100644 --- a/cetlvast/suites/compile/test_unbounded_variant_footprint_set.cpp +++ b/cetlvast/suites/compile/test_unbounded_variant_footprint_set.cpp @@ -8,19 +8,10 @@ /// #include "cetl/unbounded_variant.hpp" +#include "cetlvast/helpers_rtti.hpp" #include -namespace cetl -{ -template <> -constexpr type_id type_id_value{}; - -template <> -constexpr type_id type_id_value{}; - -} // namespace cetl - int main() { using ub_var = cetl::unbounded_variant; diff --git a/cetlvast/suites/docs/examples/example_10_unbounded_variant.cpp b/cetlvast/suites/docs/examples/example_10_unbounded_variant.cpp index 00ca124..8f5cdd4 100644 --- a/cetlvast/suites/docs/examples/example_10_unbounded_variant.cpp +++ b/cetlvast/suites/docs/examples/example_10_unbounded_variant.cpp @@ -13,14 +13,27 @@ //! [example_10_unbounded_variant_type_id] namespace cetl { + template <> -constexpr type_id type_id_value = {1}; +constexpr type_id type_id_getter() noexcept +{ + return {1}; +} template <> -constexpr type_id type_id_value = {2}; +constexpr type_id type_id_getter() noexcept +{ + return {2}; +} template <> -constexpr type_id type_id_value = {3}; +constexpr type_id type_id_getter() noexcept +{ + return {3}; +} template <> -constexpr type_id type_id_value = {4}; +constexpr type_id type_id_getter() noexcept +{ + return {4}; +} } // namespace cetl //! [example_10_unbounded_variant_type_id] diff --git a/cetlvast/suites/unittest/test_unbounded_variant.cpp b/cetlvast/suites/unittest/test_unbounded_variant.cpp index 0f672b1..40b07f6 100644 --- a/cetlvast/suites/unittest/test_unbounded_variant.cpp +++ b/cetlvast/suites/unittest/test_unbounded_variant.cpp @@ -7,6 +7,7 @@ /// SPDX-License-Identifier: MIT #include +#include #include #include @@ -28,6 +29,7 @@ using cetl::get_if; using cetl::make_unbounded_variant; using cetl::type_id; using cetl::type_id_type; +using cetl::type_id_value; using cetl::rtti_helper; using testing::_; @@ -94,7 +96,7 @@ struct side_effect_stats } }; -struct MyBase : rtti_helper> +struct MyBase : rtti_helper> { char payload_; int value_ = 0; @@ -186,7 +188,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 +227,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 +265,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 +384,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_value); + 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 +402,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_value); + 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,13 +428,23 @@ 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(), type_id_value); + + ub_var dst{src}; + EXPECT_THAT(src.type_size(), sizeof(int)); + EXPECT_THAT(src.type_id(), type_id_value); + EXPECT_THAT(dst.type_size(), sizeof(int)); + EXPECT_THAT(dst.type_id(), type_id_value); EXPECT_THAT(get(src), 42); EXPECT_THAT(get(dst), 42); const ub_var empty{}; - ub_var dst2{empty}; + EXPECT_THAT(empty.type_size(), 0); + EXPECT_THAT(empty.type_id(), type_id_value); + + ub_var dst2{empty}; EXPECT_THAT(dst2.has_value(), false); dst2 = {}; EXPECT_THAT(dst2.has_value(), false); @@ -1011,6 +1029,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(), type_id_value); auto& test_base1 = get(test_ubv); EXPECT_THAT(test_base1.payload_, 'Y'); @@ -1020,6 +1040,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(), type_id_value); auto& test_base2 = get(test_ubv); EXPECT_THAT(test_base2.payload_, 'X'); @@ -1166,6 +1188,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_value); EXPECT_THAT(stats.constructs, 1); EXPECT_THAT(stats.destructs, 0); t.reset(); @@ -1402,6 +1426,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_value); EXPECT_THAT(stats.ops, "@"); } EXPECT_THAT(stats.constructs, stats.destructs); @@ -1726,44 +1752,15 @@ 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}; - +constexpr type_id type_id_getter>() noexcept +{ + return {0xB3, 0xB8, 0x4E, 0xC1, 0x1F, 0xE4, 0x49, 0x35, 0x9E, 0xC9, 0x1A, 0x77, 0x7B, 0x82, 0x53, 0x25}; +} template <> -constexpr type_id type_id_value> = {12}; +constexpr type_id type_id_getter() noexcept +{ + return {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..0373b8f 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 (reserved for `type_id_value` specialization). template using type_id_type = std::integer_sequence; @@ -75,11 +76,30 @@ constexpr type_id type_id_type_value() noexcept return detail::type_id_type_value_impl(TypeIDType{}); } +/// The type ID getter for the given type. +/// This helper is provided for regularity; it returns the same value as \c T::_get_type_id_(). +/// The type shall satisfy \ref cetl::has_type_id. +/// Specialize this getter to add RTTI support to types where it is not possible to define a static method +/// (e.g., builtins, pointers, third-party classes, etc). +template +constexpr type_id type_id_getter() noexcept +{ + return T::_get_type_id_(); +} + +/// The type ID getter specialization reserved for `void` type - returns all zeros. +/// +template <> +constexpr type_id type_id_getter() noexcept +{ + return {}; +} + /// The type ID value of the given type. /// This helper is provided for regularity; it has the same value as \c T::_get_type_id_(). /// The type shall satisfy \ref cetl::has_type_id. template -constexpr type_id type_id_value = T::_get_type_id_(); +constexpr type_id type_id_value = type_id_getter(); /// 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..77734c9 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,57 @@ 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) -> ValueConstPtrAndTypeId { + 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) -> void* { + 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) -> ValueConstPtrAndTypeId { + 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) -> void* { + 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_value` if storage is empty. + /// + CETL_NODISCARD cetl::type_id type_id() const noexcept + { + if (!has_value()) + { + return cetl::type_id_value; + } + 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 +619,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 +633,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 +663,7 @@ struct base_access : base_storage } value_destroyer_ = nullptr; - value_converter_ = nullptr; + value_mut_converter_ = nullptr; value_const_converter_ = nullptr; base::reset(); @@ -644,10 +675,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_value` (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 +1043,8 @@ class unbounded_variant : detail::base_move