diff --git a/.github/workflows/cetlvast.yml b/.github/workflows/cetlvast.yml index 47230987..503f1fff 100644 --- a/.github/workflows/cetlvast.yml +++ b/.github/workflows/cetlvast.yml @@ -40,7 +40,7 @@ jobs: - name: Cache ext modules id: cetlvast-ext - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cetlvast-ext-cache with: @@ -85,7 +85,7 @@ jobs: - uses: actions/checkout@v4 - name: Cache ext modules id: cetlvast-ext - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cetlvast-ext-cache with: @@ -136,7 +136,7 @@ jobs: - name: Cache ext modules id: cetlvast-ext - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cetlvast-ext-cache with: diff --git a/cetlvast/cmake/compiler_flag_sets/default.cmake b/cetlvast/cmake/compiler_flag_sets/default.cmake index a238ae5a..c60c6088 100644 --- a/cetlvast/cmake/compiler_flag_sets/default.cmake +++ b/cetlvast/cmake/compiler_flag_sets/default.cmake @@ -53,6 +53,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Release") message(STATUS "Release build. Setting optimization flags.") list(APPEND C_FLAG_SET "-O1" + "-fno-delete-null-pointer-checks" # https://github.com/OpenCyphal-Garage/libcyphal/pull/347#discussion_r1572254288 ) else() diff --git a/cetlvast/include/cetlvast/memory_resource_mock.hpp b/cetlvast/include/cetlvast/memory_resource_mock.hpp new file mode 100644 index 00000000..cba9619b --- /dev/null +++ b/cetlvast/include/cetlvast/memory_resource_mock.hpp @@ -0,0 +1,66 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef CETLVAST_MEMORY_RESOURCE_MOCK_HPP_INCLUDED +#define CETLVAST_MEMORY_RESOURCE_MOCK_HPP_INCLUDED + +#include "cetl/pf17/cetlpf.hpp" +#include "cetl/pmr/memory.hpp" + +#include + +namespace cetlvast +{ + +class MemoryResourceMock : public cetl::pmr::memory_resource +{ +public: + cetl::pmr::memory_resource* resource() noexcept + { + return this; + } + + MOCK_METHOD(void*, do_allocate, (std::size_t, std::size_t), (override)); + MOCK_METHOD(void, do_deallocate, (void*, std::size_t, std::size_t)); + // NOLINTNEXTLINE(bugprone-exception-escape) + MOCK_METHOD(bool, do_is_equal, (const memory_resource&), (const, noexcept, override)); + +#if (__cplusplus < CETL_CPP_STANDARD_17) + // NOLINTNEXTLINE(bugprone-exception-escape) + MOCK_METHOD(std::size_t, do_max_size, (), (const, noexcept, override)); + MOCK_METHOD(void*, do_reallocate, (void*, std::size_t, std::size_t, std::size_t), (override)); +#endif + + void redirectExpectedCallsTo(cetl::pmr::memory_resource& mr) + { + using ::testing::_; + + EXPECT_CALL(*this, do_allocate(_, _)) + .WillRepeatedly([&mr](std::size_t size_bytes, std::size_t alignment) -> void* { + return mr.allocate(size_bytes, alignment); + }); + EXPECT_CALL(*this, do_deallocate(_, _, _)) + .WillRepeatedly([&mr](void* p, std::size_t size_bytes, std::size_t alignment) { + mr.deallocate(p, size_bytes, alignment); + }); + EXPECT_CALL(*this, do_is_equal(_)).WillRepeatedly([&mr](const memory_resource& rhs) { + return mr.is_equal(rhs); + }); + +#if (__cplusplus < CETL_CPP_STANDARD_17) + EXPECT_CALL(*this, do_max_size()).WillRepeatedly([&mr]() { return mr.max_size(); }); + EXPECT_CALL(*this, do_reallocate(_, _, _, _)) + .WillRepeatedly( + [&mr](void* ptr, std::size_t old_size_bytes, std::size_t new_size_bytes, std::size_t alignment) { + return mr.reallocate(ptr, old_size_bytes, new_size_bytes, alignment); + }); +#endif + } + +}; // MemoryResourceMock + +} // namespace cetlvast + +#endif // CETLVAST_MEMORY_RESOURCE_MOCK_HPP_INCLUDED diff --git a/cetlvast/include/cetlvast/tracking_memory_resource.hpp b/cetlvast/include/cetlvast/tracking_memory_resource.hpp new file mode 100644 index 00000000..092abfae --- /dev/null +++ b/cetlvast/include/cetlvast/tracking_memory_resource.hpp @@ -0,0 +1,94 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef CETLVAST_TRACKING_MEMORY_RESOURCE_HPP_INCLUDED +#define CETLVAST_TRACKING_MEMORY_RESOURCE_HPP_INCLUDED + +#include "cetl/pf17/cetlpf.hpp" +#include "cetl/pmr/memory.hpp" + +#include +#include + +namespace cetlvast +{ + +class TrackingMemoryResource final : public cetl::pmr::memory_resource +{ +public: + struct Allocation final + { + std::size_t size; + void* pointer; + + friend void PrintTo(const Allocation& alloc, std::ostream* os) + { + *os << "\n{ptr=0x" << std::hex << alloc.pointer << ", size=" << std::dec << alloc.size << "}"; + } + }; + + // NOLINTBEGIN + std::vector allocations{}; + std::size_t total_allocated_bytes = 0; + std::size_t total_deallocated_bytes = 0; + // NOLINTEND + +private: + // MARK: cetl::pmr::memory_resource + + void* do_allocate(std::size_t size_bytes, std::size_t alignment) override + { + if (alignment > alignof(std::max_align_t)) + { +#if defined(__cpp_exceptions) + throw std::bad_alloc(); +#endif + return nullptr; + } + + auto ptr = std::malloc(size_bytes); + + total_allocated_bytes += size_bytes; + allocations.push_back({size_bytes, ptr}); + + return ptr; + } + + void do_deallocate(void* ptr, std::size_t size_bytes, std::size_t) override + { + auto prev_alloc = std::find_if(allocations.cbegin(), allocations.cend(), [ptr](const auto& alloc) { + return alloc.pointer == ptr; + }); + if (prev_alloc != allocations.cend()) + { + allocations.erase(prev_alloc); + } + total_deallocated_bytes += size_bytes; + + std::free(ptr); + } + +#if (__cplusplus < CETL_CPP_STANDARD_17) + + void* do_reallocate(void* ptr, std::size_t old_size_bytes, std::size_t new_size_bytes, std::size_t) override + { + total_allocated_bytes -= old_size_bytes; + total_allocated_bytes += new_size_bytes; + + return std::realloc(ptr, new_size_bytes); + } + +#endif + + bool do_is_equal(const memory_resource& rhs) const noexcept override + { + return (&rhs == this); + } + +}; // TrackingMemoryResource + +} // namespace cetlvast + +#endif // CETLVAST_TRACKING_MEMORY_RESOURCE_HPP_INCLUDED 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 7a7de4fb..1e82ed01 100644 --- a/cetlvast/suites/compile/test_unbounded_variant_footprint_get_const.cpp +++ b/cetlvast/suites/compile/test_unbounded_variant_footprint_get_const.cpp @@ -29,7 +29,7 @@ int main() #ifndef CETLVAST_COMPILETEST_PRECHECK - // Verify at `cetl::detail::base_storage::get_ptr const` + // Verify at `cetl::detail::base_storage::check_footprint` (called from `base_access::get_ptr() const`). // ``` // static_assert(sizeof(ValueType) <= Footprint, // "Cannot contain the requested type since the footprint is too small"); 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 c8f6487a..c118861b 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 @@ -29,7 +29,7 @@ int main() #ifndef CETLVAST_COMPILETEST_PRECHECK - // Verify at `cetl::detail::base_storage::get_ptr` + // Verify at `cetl::detail::base_storage::check_footprint` (called from `base_access::get_ptr()`). // ``` // static_assert(sizeof(ValueType) <= Footprint, // "Cannot contain the requested type since the footprint is too small"); diff --git a/cetlvast/suites/compile/test_unbounded_variant_footprint_set.cpp b/cetlvast/suites/compile/test_unbounded_variant_footprint_set.cpp index e4d7f512..c9663327 100644 --- a/cetlvast/suites/compile/test_unbounded_variant_footprint_set.cpp +++ b/cetlvast/suites/compile/test_unbounded_variant_footprint_set.cpp @@ -29,7 +29,7 @@ int main() #ifndef CETLVAST_COMPILETEST_PRECHECK - // Verify at `cetl::detail::base_storage::make_handlers` + // Verify at `cetl::detail::base_storage::check_footprint` (called from `base_access::make_handlers()`). // ``` // static_assert(sizeof(Tp) <= Footprint, "Enlarge the footprint"); // ``` diff --git a/cetlvast/suites/compile/test_unbounded_variant_zero_footprint_non_pmr.cpp b/cetlvast/suites/compile/test_unbounded_variant_zero_footprint_non_pmr.cpp new file mode 100644 index 00000000..f2085d1b --- /dev/null +++ b/cetlvast/suites/compile/test_unbounded_variant_zero_footprint_non_pmr.cpp @@ -0,0 +1,27 @@ +/// @file +/// Compile test that ensures it's impossible to use non-PMR `unbounded_variant` with zero footprint. +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT +/// + +#include "cetl/unbounded_variant.hpp" + +#include + +int main() +{ +#ifndef CETLVAST_COMPILETEST_PRECHECK + + cetl::unbounded_variant<0> test{}; + +#else + + cetl::unbounded_variant<1> test{}; + +#endif + + return 0; +} diff --git a/cetlvast/suites/docs/examples/example_07_polymorphic_alloc_deleter.cpp b/cetlvast/suites/docs/examples/example_07_polymorphic_alloc_deleter.cpp index fcdfc07f..6a9bdfee 100644 --- a/cetlvast/suites/docs/examples/example_07_polymorphic_alloc_deleter.cpp +++ b/cetlvast/suites/docs/examples/example_07_polymorphic_alloc_deleter.cpp @@ -9,43 +9,98 @@ /// cSpell: words emplacer #include "cetl/pf17/cetlpf.hpp" +#include "cetl/pmr/interface_ptr.hpp" #include "cetl/pmr/memory.hpp" #include #include -#include #include +class INamed +{ +public: + INamed() = default; + INamed(const INamed&) = delete; + INamed(INamed&& rhs) noexcept = delete; + INamed& operator=(INamed&&) = delete; + INamed& operator=(const INamed&) noexcept = delete; + + virtual std::string name() const = 0; + +protected: + ~INamed() = default; +}; -class MyObject final +class IDescribable : public INamed { public: - MyObject(const char* name, std::size_t name_length) - : name_(nullptr) + IDescribable() = default; + IDescribable(const IDescribable&) = delete; + IDescribable(IDescribable&& rhs) noexcept = delete; + IDescribable& operator=(IDescribable&&) = delete; + IDescribable& operator=(const IDescribable&) noexcept = delete; + + virtual std::string describe() const = 0; + +protected: + ~IDescribable() = default; +}; + +class IIdentifiable +{ +public: + IIdentifiable() = default; + IIdentifiable(const IIdentifiable&) = delete; + IIdentifiable(IIdentifiable&& rhs) noexcept = delete; + IIdentifiable& operator=(IIdentifiable&&) = delete; + IIdentifiable& operator=(const IIdentifiable&) noexcept = delete; + + virtual std::uint32_t id() const = 0; + +protected: + ~IIdentifiable() = default; +}; + +class MyObjectBase +{ +public: + MyObjectBase() + : id_{counter_++} { - name_ = static_cast(malloc(name_length + 1)); - strncpy(name_, name, name_length); - name_[name_length] = '\0'; } - MyObject(const MyObject&) = delete; - MyObject& operator=(const MyObject&) = delete; - MyObject& operator=(MyObject&&) = delete; +protected: + const std::uint32_t id_; + static std::uint32_t counter_; + friend class example_07_polymorphic_alloc_deleter; +}; +std::uint32_t MyObjectBase::counter_ = 0; - MyObject(MyObject&& rhs) noexcept - : name_(rhs.name_) +class MyObject final : private MyObjectBase, public IIdentifiable, public IDescribable +{ +public: + MyObject(const char* name, std::size_t name_length) + : name_{static_cast(malloc(name_length + 1))} { - rhs.name_ = nullptr; + strncpy(name_, name, name_length + 1); + name_[name_length] = '\0'; } + MyObject(const MyObject&) = delete; + MyObject(MyObject&& rhs) noexcept = delete; + MyObject& operator=(const MyObject&) = delete; + MyObject& operator=(MyObject&&) noexcept = delete; + ~MyObject() { - std::cout << "MyObject destructor called : " << name_ << std::endl; + std::cout << "~MyObject(name='" << name_ << "', id=" << id_ << ")" << std::endl; free(name_); } - std::string name() const + // MARK: INamed + + std::string name() const override { if (name_ == nullptr) { @@ -57,19 +112,45 @@ class MyObject final } } + // MARK: IIdentifiable + + std::uint32_t id() const override + { + return id_; + } + + // MARK: IDescribable + + std::string describe() const override + { + return name() + " is a MyObject instance."; + } + private: char* name_; }; -TEST(example_07_polymorphic_alloc_deleter, example_usage_0) +class example_07_polymorphic_alloc_deleter : public testing::Test +{ +protected: + template + using InterfacePtr = cetl::pmr::InterfacePtr; + + void SetUp() override + { + MyObjectBase::counter_ = 0; + } +}; + +TEST_F(example_07_polymorphic_alloc_deleter, example_usage_0) { //![example_usage_0] // Let's say you wanted to store a bunch of objects in a container of some sort. You can use the // cetl::pmr::PolymorphicDeleter to help you build unique_ptr's like this: using MyAllocator = cetl::pmr::polymorphic_allocator; - using MyDeleter = cetl::pmr::PolymorphicDeleter; - MyAllocator alloc{cetl::pmr::new_delete_resource()}; + using MyDeleter = cetl::pmr::PolymorphicDeleter; + MyAllocator alloc{cetl::pmr::get_default_resource()}; std::unordered_map> objects; objects.reserve(3); @@ -81,21 +162,21 @@ TEST(example_07_polymorphic_alloc_deleter, example_usage_0) std::unique_ptr object_0{alloc.allocate(1), MyDeleter{alloc, 1}}; if (nullptr != object_0) { - alloc.construct(object_0.get(), "object_0", 9U); + alloc.construct(object_0.get(), "object_0", 8U); objects.emplace(object_0->name(), std::move(object_0)); - } // else, if we're here then exceptions are turned off, but deallocation is always null-safe. + } // else, if we're here then exceptions are turned off, but deallocation is always null-safe. std::unique_ptr object_1{alloc.allocate(1), MyDeleter{alloc, 1}}; if (nullptr != object_1) { - alloc.construct(object_1.get(), "object_1", 9U); + alloc.construct(object_1.get(), "object_1", 8U); objects.emplace(object_1->name(), std::move(object_1)); } std::unique_ptr object_2{alloc.allocate(1), MyDeleter{alloc, 1}}; if (nullptr != object_2) { - alloc.construct(object_2.get(), "object_2", 9U); + alloc.construct(object_2.get(), "object_2", 8U); objects.emplace(object_2->name(), std::move(object_2)); } @@ -109,35 +190,34 @@ TEST(example_07_polymorphic_alloc_deleter, example_usage_0) //![example_usage_0] } -TEST(example_07_polymorphic_alloc_deleter, example_usage_1) +TEST_F(example_07_polymorphic_alloc_deleter, example_usage_1) { //![example_usage_1] // By using the cetl::pmr::Factory, you can simplify the code from the previous example: - cetl::pmr::polymorphic_allocator alloc{cetl::pmr::new_delete_resource()}; + cetl::pmr::polymorphic_allocator alloc{cetl::pmr::get_default_resource()}; std::unordered_map> objects; objects.reserve(6); - auto object_0 = cetl::pmr::Factory::make_unique(alloc, "object_0", 9U); + auto object_0 = cetl::pmr::Factory::make_unique(alloc, "object_0", 8U); objects.emplace(object_0->name(), std::move(object_0)); - auto object_1 = cetl::pmr::Factory::make_unique(alloc, "object_1", 9U); + auto object_1 = cetl::pmr::Factory::make_unique(alloc, "object_1", 8U); objects.emplace(object_1->name(), std::move(object_1)); - auto object_2 = cetl::pmr::Factory::make_unique(alloc, "object_2", 9U); + auto object_2 = cetl::pmr::Factory::make_unique(alloc, "object_2", 8U); objects.emplace(object_2->name(), std::move(object_2)); // or even simpler: - auto emplacer = [&objects, &alloc](const char* name, std::size_t name_length) - { + auto emplacer = [&objects, &alloc](const char* name, std::size_t name_length) { auto object = cetl::pmr::Factory::make_unique(alloc, name, name_length); objects.emplace(object->name(), std::move(object)); }; - emplacer("object_3", 9U); - emplacer("object_4", 9U); - emplacer("object_5", 9U); + emplacer("object_3", 8U); + emplacer("object_4", 8U); + emplacer("object_5", 8U); for (const auto& pair : objects) { @@ -148,3 +228,42 @@ TEST(example_07_polymorphic_alloc_deleter, example_usage_1) //![example_usage_1] } + +TEST_F(example_07_polymorphic_alloc_deleter, example_usage_2) +{ + //![example_usage_2] + + cetl::pmr::polymorphic_allocator alloc{cetl::pmr::get_default_resource()}; + + auto obj0 = cetl::pmr::InterfaceFactory::make_unique(alloc, "obj0", 4U); + std::cout << "Obj0 id : " << obj0->id() << std::endl; + + // Commented b/c of current limitation of our `cetl::pmr::function`. + // Probably PMR support is needed at `cetl::unbounded_variant` (which we use inside the `function`), + // so that it will be possible to nest one deleter inside another one. + auto obj1 = cetl::pmr::InterfaceFactory::make_unique(alloc, "obj1", 4U); + { + std::cout << "Obj1 id : " << obj1->id() << std::endl; + obj1.reset(); + std::cout << std::endl; + } + + auto obj2 = cetl::pmr::InterfaceFactory::make_unique(alloc, "obj2", 4U); + { + std::cout << "Obj2 desc : " << obj2->describe() << std::endl; + std::cout << "Obj2 name_a : " << obj2->name() << std::endl; + + // Such interface ptr upcasting currently is not supported. + // + // auto obj2_named = InterfacePtr{std::move(obj2)}; + // std::cout << "Obj2 name_b : " << obj2_named->name() << std::endl; + } + + auto obj3 = cetl::pmr::InterfaceFactory::make_unique(alloc, "obj3", 4U); + { + std::cout << "Obj3 name : " << obj3->name() << std::endl; + std::cout << std::endl; + } + + //![example_usage_2] +} diff --git a/cetlvast/suites/unittest/CMakeLists.txt b/cetlvast/suites/unittest/CMakeLists.txt index 3ec901c5..4eb603d4 100644 --- a/cetlvast/suites/unittest/CMakeLists.txt +++ b/cetlvast/suites/unittest/CMakeLists.txt @@ -19,11 +19,11 @@ endif() # We generate individual test binaires so we can record which test generated # what coverage. We also allow test authors to generate coverage reports for # just one test allowing for faster iteration. -file(GLOB NATIVE_TESTS +file(GLOB_RECURSE NATIVE_TESTS LIST_DIRECTORIES false CONFIGURE_DEPENDS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} - test_*.cpp + test_*.cpp **/test_*.cpp ) set(ALL_TESTS_BUILD "") diff --git a/cetlvast/suites/unittest/pmr/test_pmr_function.cpp b/cetlvast/suites/unittest/pmr/test_pmr_function.cpp new file mode 100644 index 00000000..4798c914 --- /dev/null +++ b/cetlvast/suites/unittest/pmr/test_pmr_function.cpp @@ -0,0 +1,460 @@ +/// @file +/// Unit tests for cetl/pmr/function.hpp +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#include +#include +#include + +#include +#include +#include + +namespace +{ + +using cetl::pmr::function; + +using testing::Not; +using testing::IsEmpty; + +class TestPmrFunction : public testing::Test +{ +protected: + using pmr = cetl::pmr::memory_resource; + + void TearDown() override + { + EXPECT_THAT(mr_.allocations, IsEmpty()); + EXPECT_THAT(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); + } + + static std::string print_num(int i) + { + return testing::PrintToString(i); + } + + pmr* get_mr() noexcept + { + return &mr_; + } + + pmr* get_default_mr() noexcept + { + return cetl::pmr::get_default_resource(); + } + + cetlvast::TrackingMemoryResource mr_; +}; + +TEST_F(TestPmrFunction, cpp_reference) +{ + struct Foo + { + Foo(int num) + : num_{num} + { + } + std::string print_add(int i) const + { + return print_num(num_ + i); + } + int num_; + }; + struct PrintNum + { + std::string operator()(int i) const + { + return print_num(i); + } + }; + + // store a free function + function f_display = print_num; + EXPECT_THAT(f_display(-9), "-9"); + + // store a lambda + function f_display_42 = []() { return print_num(42); }; + EXPECT_THAT(f_display_42(), "42"); + + // store the result of a call to std::bind + function f_display_31337 = std::bind(print_num, 31337); + EXPECT_THAT(f_display_31337(), "31337"); + + // store a call to a member function + // function f_add_display = &Foo::print_add; + const Foo foo(314159); + // f_add_display(foo, 1); + // f_add_display(314159, 1); + + // store a call to a data member accessor + // function f_num = &Foo::num_; + // EXPECT_THAT(f_num(foo), 314159); + + // store a call to a member function and object + using std::placeholders::_1; + function f_add_display2 = std::bind(&Foo::print_add, foo, _1); + f_add_display2(2); + + // store a call to a member function and object ptr + function f_add_display3 = std::bind(&Foo::print_add, &foo, _1); + f_add_display3(3); + + // store a call to a function object + function f_display_obj = PrintNum(); + EXPECT_THAT(f_display_obj(18), "18"); + + auto factorial = [](int n) { + // store a lambda object to emulate "recursive lambda"; aware of extra overhead + function fac = [&](int _n) { return (_n < 2) ? 1 : _n * fac(_n - 1); }; + return fac(n); + }; + EXPECT_THAT(factorial(1), 1); + EXPECT_THAT(factorial(2), 1 * 2); + EXPECT_THAT(factorial(3), 1 * 2 * 3); + EXPECT_THAT(factorial(4), 1 * 2 * 3 * 4); + EXPECT_THAT(factorial(5), 1 * 2 * 3 * 4 * 5); + EXPECT_THAT(factorial(6), 1 * 2 * 3 * 4 * 5 * 6); + EXPECT_THAT(factorial(7), 1 * 2 * 3 * 4 * 5 * 6 * 7); +} + +TEST_F(TestPmrFunction, ctor_1_default) +{ + const function f1{}; + EXPECT_THAT(!f1, Not(false)); + EXPECT_THAT(static_cast(f1), false); +} + +TEST_F(TestPmrFunction, ctor_3_copy) +{ + using str_function = function; + + const str_function fn{std::bind(print_num, 123)}; + + str_function fn_copy{fn}; + EXPECT_THAT(!!fn_copy, true); + EXPECT_THAT(static_cast(fn_copy), true); + EXPECT_THAT(fn_copy(), "123"); + + fn_copy = {}; + const str_function fn_empty{fn_copy}; + EXPECT_THAT(!!fn_empty, false); +} + +TEST_F(TestPmrFunction, ctor_4_move) +{ + using str_function = function; + + str_function fn{[]() { return print_num(123); }}; + + str_function fn_moved{std::move(fn)}; + EXPECT_THAT(!!fn_moved, true); + EXPECT_THAT(static_cast(fn_moved), true); + EXPECT_THAT(fn_moved(), "123"); + + fn_moved = {}; + const str_function fn_empty{std::move(fn_moved)}; + EXPECT_THAT(!!fn_empty, false); +} + +TEST_F(TestPmrFunction, ctor_5_functor_lambda) +{ + using str_function = function; + + str_function fn{[]() { return print_num(123); }}; + EXPECT_THAT(!!fn, true); + EXPECT_THAT(fn(), "123"); +} + +TEST_F(TestPmrFunction, assign_1_copy) +{ + using str_function = function; + + const str_function fn1{[]() { return print_num(123); }}; + EXPECT_THAT(!!fn1, true); + + const str_function fn2 = fn1; + EXPECT_THAT(!!fn1, true); + EXPECT_THAT(!!fn2, true); + EXPECT_THAT(fn2(), "123"); +} + +TEST_F(TestPmrFunction, assign_2_move) +{ + using str_function = function; + + str_function fn1{[]() { return print_num(123); }}; + EXPECT_THAT(!!fn1, true); + + const str_function fn2 = std::move(fn1); + EXPECT_THAT(!!fn1, false); + EXPECT_THAT(!!fn2, true); + EXPECT_THAT(fn2(), "123"); +} + +TEST_F(TestPmrFunction, assign_3_nullptr) +{ + using str_function = function; + str_function fn{[]() { return print_num(123); }}; + EXPECT_THAT(!!fn, true); + + fn = nullptr; + EXPECT_THAT(!!fn, false); +} + +TEST_F(TestPmrFunction, assign_4_rv_functor) +{ + function f1; + f1 = [](const std::string& rhs) { return "A" + rhs; }; + EXPECT_THAT(f1("x"), "Ax"); + + function f2; + f2 = [f1](const std::string& rhs) { return f1(rhs) + "B"; }; + EXPECT_THAT(f2("x"), "AxB"); + + // Note: we move assign different type of function (Footprint and Pmr). + function f0{get_default_mr(), [](const std::string&) { return "123"; }}; + f2 = std::move(f0); + EXPECT_THAT(!!f0, false); + EXPECT_THAT(f2("x"), "123"); +} + +TEST_F(TestPmrFunction, assign_4_lv_functor) +{ + function f1; + auto l1 = [](const std::string& rhs) { return "A" + rhs; }; + f1 = l1; + EXPECT_THAT(f1("x"), "Ax"); + + function f2; + auto l2 = [f1](const std::string& rhs) { return f1(rhs) + "B"; }; + f2 = l2; + EXPECT_THAT(f2("x"), "AxB"); + + // Note: we copy assign different type of function (Footprint and Pmr). + const function f0{get_default_mr(), + [](const std::string&) { return "123"; }}; + f2 = f0; + EXPECT_THAT(!!f0, true); + EXPECT_THAT(f0.get_memory_resource(), get_default_mr()); + EXPECT_THAT(f2("x"), "123"); +} + +TEST_F(TestPmrFunction, swap) +{ + using str_function = function; + + str_function fn1{[]() { return print_num(123); }}; + str_function fn2{[]() { return print_num(456); }}; + + EXPECT_THAT(fn1(), "123"); + EXPECT_THAT(fn2(), "456"); + + std::swap(fn1, fn2); + + EXPECT_THAT(fn1(), "456"); + EXPECT_THAT(fn2(), "123"); +} + +TEST_F(TestPmrFunction, pmr_ctor_1_default) +{ + const function f1{get_default_mr()}; + EXPECT_THAT(!f1, Not(false)); + EXPECT_THAT(static_cast(f1), false); + EXPECT_THAT(f1.get_memory_resource(), get_default_mr()); +} + +TEST_F(TestPmrFunction, pmr_ctor_3_copy) +{ + using str_function = function; + const str_function fn{get_mr(), std::bind(print_num, 123)}; + + str_function fn_copy{fn}; + EXPECT_THAT(!!fn_copy, true); + EXPECT_THAT(static_cast(fn_copy), true); + EXPECT_THAT(fn_copy.get_memory_resource(), get_mr()); + EXPECT_THAT(fn_copy(), "123"); + + fn_copy = {}; + const str_function fn_empty{fn_copy}; + EXPECT_THAT(!!fn_empty, false); +} + +TEST_F(TestPmrFunction, pmr_ctor_4_move) +{ + using str_function = function; + str_function fn{get_mr(), []() { return print_num(123); }}; + + str_function fn_moved{std::move(fn)}; + EXPECT_THAT(!!fn_moved, true); + EXPECT_THAT(static_cast(fn_moved), true); + EXPECT_THAT(fn_moved(), "123"); + EXPECT_THAT(fn_moved.get_memory_resource(), get_mr()); + + fn_moved = {}; + const str_function fn_empty{std::move(fn_moved)}; + EXPECT_THAT(!!fn_empty, false); +} + +TEST_F(TestPmrFunction, pmr_ctor_5_lambda_default_mr_) +{ + using str_function = function; + str_function fn{get_default_mr(), []() { return print_num(123); }}; + EXPECT_THAT(!!fn, true); + EXPECT_THAT(fn(), "123"); + EXPECT_THAT(fn.get_memory_resource(), get_default_mr()); +} + +TEST_F(TestPmrFunction, pmr_ctor_5_lambda_custom_mr_) +{ + using str_function = function; + str_function fn{get_mr(), []() { return print_num(123); }}; + EXPECT_THAT(!!fn, true); + EXPECT_THAT(fn(), "123"); + EXPECT_THAT(fn.get_memory_resource(), get_mr()); +} + +TEST_F(TestPmrFunction, pmr_ctor_memory_resource) +{ + const function f1{get_mr()}; + EXPECT_THAT(!f1, Not(false)); + EXPECT_THAT(static_cast(f1), false); +} + +TEST_F(TestPmrFunction, pmr_assign_1_copy_fit) +{ + using str_function = function; + + const str_function fn1{get_mr(), []() { return print_num(123); }}; + EXPECT_THAT(!!fn1, true); + + str_function fn2 = fn1; + EXPECT_THAT(!!fn1, true); + EXPECT_THAT(fn1.get_memory_resource(), get_mr()); + EXPECT_THAT(!!fn2, true); + EXPECT_THAT(fn2(), "123"); + EXPECT_THAT(fn2.get_memory_resource(), get_mr()); + + fn2 = {}; + EXPECT_THAT(!!fn1, true); + EXPECT_THAT(fn1.get_memory_resource(), get_mr()); + EXPECT_THAT(!!fn2, false); +} + +TEST_F(TestPmrFunction, pmr_assign_1_copy_nofit) +{ + using str_function = function; + + const str_function fn1{get_mr(), []() { return print_num(123); }}; + EXPECT_THAT(!!fn1, true); + + str_function fn2 = fn1; + EXPECT_THAT(!!fn1, true); + EXPECT_THAT(fn1.get_memory_resource(), get_mr()); + EXPECT_THAT(!!fn2, true); + EXPECT_THAT(fn2(), "123"); + EXPECT_THAT(fn2.get_memory_resource(), get_mr()); + + fn2 = {}; + EXPECT_THAT(!!fn1, true); + EXPECT_THAT(fn1.get_memory_resource(), get_mr()); + EXPECT_THAT(!!fn2, false); + EXPECT_THAT(fn2.get_memory_resource(), get_mr()); +} + +TEST_F(TestPmrFunction, pmr_assign_2_move_fit) +{ + using str_function = function; + + str_function fn1{get_mr(), []() { return print_num(123); }}; + EXPECT_THAT(!!fn1, true); + + str_function fn2 = std::move(fn1); + EXPECT_THAT(!!fn1, false); + EXPECT_THAT(!!fn2, true); + EXPECT_THAT(fn2(), "123"); + + fn2 = {}; + EXPECT_THAT(!!fn2, false); + EXPECT_THAT(fn2.get_memory_resource(), get_mr()); +} + +TEST_F(TestPmrFunction, pmr_assign_2_move_nofit) +{ + using str_function = function; + + str_function fn1{get_mr(), []() { return print_num(123); }}; + EXPECT_THAT(!!fn1, true); + + str_function fn2 = std::move(fn1); + EXPECT_THAT(!!fn1, false); + EXPECT_THAT(!!fn2, true); + EXPECT_THAT(fn2(), "123"); + + fn2 = {}; + EXPECT_THAT(!!fn2, false); + EXPECT_THAT(fn2.get_memory_resource(), get_mr()); +} + +TEST_F(TestPmrFunction, pmr_assign_3_nullptr) +{ + using str_function = function; + str_function fn{get_mr(), []() { return print_num(123); }}; + EXPECT_THAT(!!fn, true); + + fn = nullptr; + EXPECT_THAT(!!fn, false); + EXPECT_THAT(fn.get_memory_resource(), get_mr()); +} + +TEST_F(TestPmrFunction, pmr_assign_4_rv_functor) +{ + using str_function = function; + + str_function f1{get_mr()}; + f1 = [](const std::string& rhs) { return "A" + rhs; }; + EXPECT_THAT(f1("x"), "Ax"); + EXPECT_THAT(f1.get_memory_resource(), get_mr()); + + str_function f2{get_mr()}; + f2 = [f1](const std::string& rhs) { return f1(rhs) + "B"; }; + EXPECT_THAT(f2("x"), "AxB"); + EXPECT_THAT(f2.get_memory_resource(), get_mr()); + + // Note: we assign different type of function (different Footprint and Pmr). + function f0{[](const std::string&) { return "123"; }}; + f2 = std::move(f0); + EXPECT_THAT(!!f0, false); + EXPECT_THAT(f2("x"), "123"); + EXPECT_THAT(f2.get_memory_resource(), get_mr()); +} + +TEST_F(TestPmrFunction, pmr_assign_4_lv_functor) +{ + using str_function = function; + + str_function f1{get_mr()}; + auto l1 = [](const std::string& rhs) { return "A" + rhs; }; + f1 = l1; + EXPECT_THAT(f1("x"), "Ax"); + EXPECT_THAT(f1.get_memory_resource(), get_mr()); + + str_function f2{get_mr()}; + auto l2 = [f1](const std::string& rhs) { return f1(rhs) + "B"; }; + f2 = l2; + EXPECT_THAT(f2("x"), "AxB"); + EXPECT_THAT(f2.get_memory_resource(), get_mr()); + + // Note: we copy assign different type of function (Footprint and Pmr). + const function f0{[](const std::string&) { return "123"; }}; + f2 = f0; + EXPECT_THAT(!!f0, true); + EXPECT_THAT(f2("x"), "123"); + EXPECT_THAT(f2.get_memory_resource(), get_mr()); +} + +} // namespace diff --git a/cetlvast/suites/unittest/pmr/test_pmr_interface_ptr.cpp b/cetlvast/suites/unittest/pmr/test_pmr_interface_ptr.cpp new file mode 100644 index 00000000..a491dffe --- /dev/null +++ b/cetlvast/suites/unittest/pmr/test_pmr_interface_ptr.cpp @@ -0,0 +1,238 @@ +/// @file +/// Unit tests for cetl/pmr/interface_ptr.hpp +/// +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#include +#include +#include + +#include +#include +#include + +namespace +{ + +using testing::_; +using testing::IsNull; +using testing::Return; +using testing::IsEmpty; +using testing::NotNull; +using testing::StrictMock; + +#if defined(__cpp_exceptions) + +// Workaround for GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425 +// Should be used in the tests where exceptions are expected (see `EXPECT_THROW`). +const auto sink = [](auto&&) {}; + +#endif + +class INamed +{ +public: + INamed() = default; + INamed(const INamed&) = delete; + INamed(INamed&& rhs) noexcept = delete; + INamed& operator=(INamed&&) = delete; + INamed& operator=(const INamed&) noexcept = delete; + + virtual std::string name() const = 0; + +protected: + ~INamed() = default; +}; + +class IDescribable : public INamed +{ +public: + IDescribable() = default; + IDescribable(const IDescribable&) = delete; + IDescribable(IDescribable&& rhs) noexcept = delete; + IDescribable& operator=(IDescribable&&) = delete; + IDescribable& operator=(const IDescribable&) noexcept = delete; + + virtual std::string describe() const = 0; + +protected: + ~IDescribable() = default; +}; + +class IIdentifiable +{ +public: + IIdentifiable() = default; + IIdentifiable(const IIdentifiable&) = delete; + IIdentifiable(IIdentifiable&& rhs) noexcept = delete; + IIdentifiable& operator=(IIdentifiable&&) = delete; + IIdentifiable& operator=(const IIdentifiable&) noexcept = delete; + + virtual std::uint32_t id() const = 0; + +protected: + ~IIdentifiable() = default; +}; + +class MyObjectBase +{ +public: + MyObjectBase() + : id_{counter_++} + { + } + +protected: + const std::uint32_t id_; + static std::uint32_t counter_; + friend class TestPmrInterfacePtr; +}; +std::uint32_t MyObjectBase::counter_ = 0; + +class MyObject final : private MyObjectBase, public IIdentifiable, public IDescribable +{ +public: + MyObject(std::string name, bool throw_on_ctor = false) + : name_{std::move(name)} + { + if (throw_on_ctor) + { +#if defined(__cpp_exceptions) + throw std::runtime_error("ctor"); +#endif + } + } + + ~MyObject() = default; + MyObject(const MyObject&) = delete; + MyObject(MyObject&& rhs) noexcept = delete; + MyObject& operator=(const MyObject&) = delete; + MyObject& operator=(MyObject&&) noexcept = delete; + + // MARK: INamed + + std::string name() const override + { + return name_; + } + + // MARK: IIdentifiable + + std::uint32_t id() const override + { + return id_; + } + + // MARK: IDescribable + + std::string describe() const override + { + return name() + " is a MyObject instance."; + } + +private: + std::string name_; +}; + +class TestPmrInterfacePtr : public testing::Test +{ +protected: + using pmr = cetl::pmr::memory_resource; + + void SetUp() override + { + MyObjectBase::counter_ = 0; + } + + void TearDown() override + { + EXPECT_THAT(mr_.allocations, IsEmpty()); + EXPECT_THAT(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); + } + + pmr* get_mr() noexcept + { + return &mr_; + } + + cetlvast::TrackingMemoryResource mr_; +}; + +TEST_F(TestPmrInterfacePtr, make_unique_concrete) +{ + cetl::pmr::polymorphic_allocator alloc{get_mr()}; + + auto obj0 = cetl::pmr::InterfaceFactory::make_unique(alloc, "obj0"); + EXPECT_THAT(obj0, NotNull()); + EXPECT_THAT(obj0->name(), "obj0"); + EXPECT_THAT(obj0->describe(), "obj0 is a MyObject instance."); +} + +TEST_F(TestPmrInterfacePtr, make_unique_interface) +{ + cetl::pmr::polymorphic_allocator alloc{get_mr()}; + + auto obj0 = cetl::pmr::InterfaceFactory::make_unique(alloc, "obj0"); + EXPECT_THAT(obj0, NotNull()); + EXPECT_THAT(obj0->name(), "obj0"); + EXPECT_THAT(obj0->describe(), "obj0 is a MyObject instance."); + + obj0.reset(); +} + +TEST_F(TestPmrInterfacePtr, up_cast_interface) +{ + cetl::pmr::polymorphic_allocator alloc{get_mr()}; + + auto obj0 = cetl::pmr::InterfaceFactory::make_unique(alloc, "obj0"); + EXPECT_THAT(obj0, NotNull()); + EXPECT_THAT(obj0->name(), "obj0"); + EXPECT_THAT(obj0->describe(), "obj0 is a MyObject instance."); + + const INamed& obj0_named = *obj0; + + EXPECT_THAT(obj0, NotNull()); + EXPECT_THAT(obj0_named.name(), "obj0"); + + obj0.reset(); +} + +TEST_F(TestPmrInterfacePtr, make_unique_out_of_memory) +{ + StrictMock mr_mock{}; + + cetl::pmr::polymorphic_allocator alloc{&mr_mock}; + + EXPECT_CALL(mr_mock, do_allocate(sizeof(MyObject), _)).WillOnce(Return(nullptr)); + + auto obj0 = cetl::pmr::InterfaceFactory::make_unique(alloc, "obj0"); + EXPECT_THAT(obj0, IsNull()); +} + +TEST_F(TestPmrInterfacePtr, make_unique_myobj_ctor_throws) +{ + StrictMock mr_mock{}; + + cetl::pmr::polymorphic_allocator alloc{&mr_mock}; + + EXPECT_CALL(mr_mock, do_allocate(sizeof(MyObject), _)) + .WillOnce( + [this](std::size_t size_bytes, std::size_t alignment) { return mr_.allocate(size_bytes, alignment); }); + EXPECT_CALL(mr_mock, do_deallocate(_, sizeof(MyObject), _)) + .WillOnce([this](void* p, std::size_t size_bytes, std::size_t alignment) { + mr_.deallocate(p, size_bytes, alignment); + }); + +#if defined(__cpp_exceptions) + EXPECT_THROW(sink(cetl::pmr::InterfaceFactory::make_unique(alloc, "obj0", true)), std::runtime_error); +#else + auto obj0 = cetl::pmr::InterfaceFactory::make_unique(alloc, "obj0", true); + EXPECT_THAT(obj0, NotNull()); + EXPECT_THAT(obj0->name(), "obj0"); +#endif +} + +} // namespace diff --git a/cetlvast/suites/unittest/test_unbounded_variant.cpp b/cetlvast/suites/unittest/test_unbounded_variant.cpp index 02a8b252..f2e4665c 100644 --- a/cetlvast/suites/unittest/test_unbounded_variant.cpp +++ b/cetlvast/suites/unittest/test_unbounded_variant.cpp @@ -7,11 +7,15 @@ /// SPDX-License-Identifier: MIT #include +#include +#include #include #include -#include #include +#include +#include +#include // NOLINTBEGIN(*-use-after-move) @@ -27,8 +31,12 @@ using cetl::type_id; using cetl::type_id_type; using cetl::rtti_helper; +using testing::_; +using testing::Return; using testing::IsNull; +using testing::IsEmpty; using testing::NotNull; +using testing::StrictMock; using namespace std::string_literals; @@ -61,6 +69,14 @@ struct side_effect_stats int constructs = 0; int destructs = 0; + void reset() + { + ops.clear(); + assignments = 0; + constructs = 0; + destructs = 0; + } + auto make_side_effect_fn() { return [this](side_effect_op op) { @@ -79,39 +95,39 @@ struct side_effect_stats } }; -struct TestBase : rtti_helper> +struct MyBase : rtti_helper> { char payload_; int value_ = 0; bool moved_ = false; - TestBase(const char payload, side_effect_fn side_effect) + MyBase(const char payload, side_effect_fn side_effect) : payload_(payload) , side_effect_(std::move(side_effect)) { side_effect_(side_effect_op::Construct); } - TestBase(const TestBase& other) + MyBase(const MyBase& other) { copy_from(other, side_effect_op::CopyConstruct); } - TestBase(TestBase&& other) noexcept + MyBase(MyBase&& other) noexcept { move_from(other, side_effect_op::MoveConstruct); } - ~TestBase() override + ~MyBase() override { side_effect_(moved_ ? side_effect_op::DestructMoved : side_effect_op::Destruct); } - TestBase& operator=(const TestBase& other) + MyBase& operator=(const MyBase& other) { copy_from(other, side_effect_op::CopyAssign); return *this; } - TestBase& operator=(TestBase&& other) noexcept + MyBase& operator=(MyBase&& other) noexcept { move_from(other, side_effect_op::MoveAssign); return *this; @@ -119,13 +135,13 @@ struct TestBase : rtti_helper> CETL_NODISCARD virtual const char* what() const noexcept { - return "TestBase"; + return "MyBase"; } private: side_effect_fn side_effect_; - void copy_from(const TestBase& other, const side_effect_op op) + void copy_from(const MyBase& other, const side_effect_op op) { payload_ = other.payload_; side_effect_ = other.side_effect_; @@ -134,7 +150,7 @@ struct TestBase : rtti_helper> side_effect_(op); } - void move_from(TestBase& other, const side_effect_op op) + void move_from(MyBase& other, const side_effect_op op) { payload_ = other.payload_; side_effect_ = other.side_effect_; @@ -146,25 +162,25 @@ struct TestBase : rtti_helper> side_effect_(op); } -}; // TestBase +}; // MyBase -struct TestCopyableOnly final : TestBase +struct MyCopyableOnly final : MyBase { - explicit TestCopyableOnly( + explicit MyCopyableOnly( const char payload = '?', side_effect_fn side_effect = [](auto) {}) - : TestBase(payload, std::move(side_effect)) + : MyBase(payload, std::move(side_effect)) { } - TestCopyableOnly(const TestCopyableOnly& other) = default; - TestCopyableOnly(TestCopyableOnly&& other) noexcept = delete; + MyCopyableOnly(const MyCopyableOnly& other) = default; + MyCopyableOnly(MyCopyableOnly&& other) noexcept = delete; - TestCopyableOnly& operator=(const TestCopyableOnly& other) = default; - TestCopyableOnly& operator=(TestCopyableOnly&& other) noexcept = delete; + MyCopyableOnly& operator=(const MyCopyableOnly& other) = default; + MyCopyableOnly& operator=(MyCopyableOnly&& other) noexcept = delete; CETL_NODISCARD const char* what() const noexcept override { - return "TestCopyableOnly"; + return "MyCopyableOnly"; } // rtti @@ -184,26 +200,26 @@ struct TestCopyableOnly final : TestBase } private: - using base = TestBase; + using base = MyBase; }; -struct TestMovableOnly final : TestBase +struct MyMovableOnly final : MyBase { - explicit TestMovableOnly( + explicit MyMovableOnly( const char payload = '?', side_effect_fn side_effect = [](auto) {}) - : TestBase(payload, std::move(side_effect)) + : MyBase(payload, std::move(side_effect)) { } - TestMovableOnly(const TestMovableOnly& other) = delete; - TestMovableOnly(TestMovableOnly&& other) noexcept = default; + MyMovableOnly(const MyMovableOnly& other) = delete; + MyMovableOnly(MyMovableOnly&& other) noexcept = default; - TestMovableOnly& operator=(const TestMovableOnly& other) = delete; - TestMovableOnly& operator=(TestMovableOnly&& other) noexcept = default; + MyMovableOnly& operator=(const MyMovableOnly& other) = delete; + MyMovableOnly& operator=(MyMovableOnly&& other) noexcept = default; CETL_NODISCARD const char* what() const noexcept override { - return "TestMovableOnly"; + return "MyMovableOnly"; } // rtti @@ -223,25 +239,25 @@ struct TestMovableOnly final : TestBase } private: - using base = TestBase; + using base = MyBase; }; -struct TestCopyableAndMovable final : TestBase +struct MyCopyableAndMovable final : MyBase { // Just to make this class a bit bigger than base. char place_holder_; - explicit TestCopyableAndMovable( + explicit MyCopyableAndMovable( const char payload = '?', side_effect_fn side_effect = [](auto) {}) - : TestBase(payload, std::move(side_effect)) + : MyBase(payload, std::move(side_effect)) , place_holder_{payload} { } CETL_NODISCARD const char* what() const noexcept override { - return "TestCopyableAndMovable"; + return "MyCopyableAndMovable"; } // rtti @@ -261,12 +277,39 @@ struct TestCopyableAndMovable final : TestBase } private: - using base = TestBase; + using base = MyBase; +}; + +struct Empty +{}; + +class TestPmrUnboundedVariant : public testing::Test +{ +protected: + using pmr = cetl::pmr::memory_resource; + + void TearDown() override + { + EXPECT_THAT(mr_.allocations, IsEmpty()); + EXPECT_THAT(mr_.total_allocated_bytes, mr_.total_deallocated_bytes); + } + + pmr* get_mr() noexcept + { + return &mr_; + } + + pmr* get_default_mr() noexcept + { + return cetl::pmr::get_default_resource(); + } + + cetlvast::TrackingMemoryResource mr_; }; /// TESTS ----------------------------------------------------------------------------------------------------------- -TEST(test_unbounded_variant, bad_unbounded_variant_access_ctor) +TEST_F(TestPmrUnboundedVariant, bad_unbounded_variant_access_ctor) { #if defined(__cpp_exceptions) @@ -285,7 +328,7 @@ TEST(test_unbounded_variant, bad_unbounded_variant_access_ctor) #endif } -TEST(test_unbounded_variant, bad_unbounded_variant_access_assignment) +TEST_F(TestPmrUnboundedVariant, bad_unbounded_variant_access_assignment) { #if defined(__cpp_exceptions) @@ -304,7 +347,7 @@ TEST(test_unbounded_variant, bad_unbounded_variant_access_assignment) #endif } -TEST(test_unbounded_variant, cppref_example) +TEST_F(TestPmrUnboundedVariant, cppref_example) { using ub_var = unbounded_variant; @@ -337,14 +380,8 @@ TEST(test_unbounded_variant, cppref_example) EXPECT_THAT(*get_if(&a), 3); } -TEST(test_unbounded_variant, ctor_1_default) +TEST_F(TestPmrUnboundedVariant, ctor_1_default) { - EXPECT_FALSE((unbounded_variant<0>{}.has_value())); - EXPECT_FALSE((unbounded_variant<0, false>{}.has_value())); - EXPECT_FALSE((unbounded_variant<0, false, true>{}.has_value())); - EXPECT_FALSE((unbounded_variant<0, true, false>{}.has_value())); - EXPECT_FALSE((unbounded_variant<0, true, true, 1>{}.has_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())); @@ -358,7 +395,25 @@ TEST(test_unbounded_variant, ctor_1_default) EXPECT_FALSE((unbounded_variant<13, true, true, 1>{}.has_value())); } -TEST(test_unbounded_variant, ctor_2_copy) +TEST_F(TestPmrUnboundedVariant, ctor_1_default_pmr) +{ + 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())); + EXPECT_FALSE((unbounded_variant<0, true, true, 8, pmr>{get_mr()}.has_value())); + + EXPECT_FALSE((unbounded_variant<1, false, false, 8, pmr>{get_mr()}.has_value())); + EXPECT_FALSE((unbounded_variant<1, false, true, 8, pmr>{get_mr()}.has_value())); + EXPECT_FALSE((unbounded_variant<1, true, false, 8, pmr>{get_mr()}.has_value())); + EXPECT_FALSE((unbounded_variant<1, true, true, 8, pmr>{get_mr()}.has_value())); + + EXPECT_FALSE((unbounded_variant<13, false, false, 8, pmr>{get_mr()}.has_value())); + EXPECT_FALSE((unbounded_variant<13, false, true, 8, pmr>{get_mr()}.has_value())); + EXPECT_FALSE((unbounded_variant<13, true, false, 8, pmr>{get_mr()}.has_value())); + EXPECT_FALSE((unbounded_variant<13, true, true, 8, pmr>{get_mr()}.has_value())); +} + +TEST_F(TestPmrUnboundedVariant, ctor_2_copy) { // Primitive `int` { @@ -369,11 +424,17 @@ TEST(test_unbounded_variant, ctor_2_copy) EXPECT_THAT(get(src), 42); EXPECT_THAT(get(dst), 42); + + const ub_var empty{}; + ub_var dst2{empty}; + EXPECT_THAT(dst2.has_value(), false); + dst2 = {}; + EXPECT_THAT(dst2.has_value(), false); } // Copyable and Movable `unbounded_variant` { - using test = TestCopyableAndMovable; + using test = MyCopyableAndMovable; using ub_var = unbounded_variant; const ub_var src{test{}}; @@ -393,7 +454,7 @@ TEST(test_unbounded_variant, ctor_2_copy) // Copyable only `unbounded_variant` { - using test = TestCopyableOnly; + using test = MyCopyableOnly; using ub_var = unbounded_variant; const test value{}; @@ -408,7 +469,7 @@ TEST(test_unbounded_variant, ctor_2_copy) // Movable only `unbounded_variant` { - using test = TestMovableOnly; + using test = MyMovableOnly; using ub_var = unbounded_variant; test value{'X'}; @@ -429,7 +490,7 @@ TEST(test_unbounded_variant, ctor_2_copy) // Non-Copyable and non-movable `unbounded_variant` { - using test = TestCopyableAndMovable; + using test = MyCopyableAndMovable; using ub_var = unbounded_variant; ub_var src{test{}}; @@ -441,7 +502,7 @@ TEST(test_unbounded_variant, ctor_2_copy) } } -TEST(test_unbounded_variant, ctor_3_move) +TEST_F(TestPmrUnboundedVariant, ctor_3_move) { // Primitive `int` { @@ -452,11 +513,15 @@ TEST(test_unbounded_variant, ctor_3_move) EXPECT_FALSE(src.has_value()); EXPECT_THAT(get(dst), 42); + + ub_var empty{}; + ub_var dst2{std::move(empty)}; + EXPECT_THAT(dst2.has_value(), false); } // Copyable and Movable `unbounded_variant` { - using test = TestCopyableAndMovable; + using test = MyCopyableAndMovable; using ub_var = unbounded_variant; ub_var src{test{}}; @@ -465,12 +530,12 @@ TEST(test_unbounded_variant, ctor_3_move) const ub_var dst{std::move(src)}; EXPECT_TRUE(dst.has_value()); EXPECT_FALSE(src.has_value()); - EXPECT_THAT(get(dst).value_, 2); + EXPECT_THAT(get(dst).value_, 2); } // Movable only `unbounded_variant` { - using test = TestMovableOnly; + using test = MyMovableOnly; using ub_var = unbounded_variant; ub_var src{test{'X'}}; @@ -479,16 +544,16 @@ TEST(test_unbounded_variant, ctor_3_move) EXPECT_THAT(get_if(&src), IsNull()); EXPECT_THAT(get(dst).value_, 2); EXPECT_THAT(get(dst).payload_, 'X'); - // EXPECT_THAT(get_if(dst).value_, 2); //< expectedly won't compile (due to !copyable) - // EXPECT_THAT(get_if(dst).value_, 2); //< expectedly won't compile (due to const) + // EXPECT_THAT(get(dst).value_, 2); //< expectedly won't compile (due to !copyable) + // EXPECT_THAT(get(dst).value_, 2); //< expectedly won't compile (due to const) } // Copyable only `unbounded_variant`, movable only `unique_ptr` { - using test = std::unique_ptr; + using test = std::unique_ptr; using ub_var = unbounded_variant; - ub_var src{std::make_unique()}; + ub_var src{std::make_unique()}; ub_var dst{std::move(src)}; EXPECT_FALSE(src.has_value()); @@ -498,9 +563,9 @@ TEST(test_unbounded_variant, ctor_3_move) } } -TEST(test_unbounded_variant, ctor_4_move_value) +TEST_F(TestPmrUnboundedVariant, ctor_4_move_value) { - using test = TestCopyableAndMovable; + using test = MyCopyableAndMovable; using ub_var = unbounded_variant; test value{'Y'}; @@ -511,51 +576,51 @@ TEST(test_unbounded_variant, ctor_4_move_value) EXPECT_THAT(get(dst).payload_, 'Y'); } -TEST(test_unbounded_variant, ctor_5_in_place) +TEST_F(TestPmrUnboundedVariant, ctor_5_in_place) { - struct TestType : rtti_helper> + struct MyType : rtti_helper> { char ch_; int number_; - TestType(const char ch, const int number) + MyType(const char ch, const int number) { ch_ = ch; number_ = number; } }; - using ub_var = unbounded_variant; + using ub_var = unbounded_variant; - const ub_var src{in_place_type_t{}, 'Y', 42}; + const ub_var src{in_place_type_t{}, 'Y', 42}; - const auto test = get(src); + const auto test = get(src); EXPECT_THAT(test.ch_, 'Y'); EXPECT_THAT(test.number_, 42); } -TEST(test_unbounded_variant, ctor_6_in_place_initializer_list) +TEST_F(TestPmrUnboundedVariant, ctor_6_in_place_initializer_list) { - struct TestType : rtti_helper> + struct MyType : rtti_helper> { std::size_t size_; int number_; - TestType(const std::initializer_list chars, const int number) + MyType(const std::initializer_list chars, const int number) { size_ = chars.size(); number_ = number; } }; - using ub_var = unbounded_variant; + using ub_var = unbounded_variant; - const ub_var src{in_place_type_t{}, {'A', 'B', 'C'}, 42}; + const ub_var src{in_place_type_t{}, {'A', 'B', 'C'}, 42}; - auto& test = get(src); + auto& test = get(src); EXPECT_THAT(test.size_, 3); EXPECT_THAT(test.number_, 42); } -TEST(test_unbounded_variant, assign_1_copy) +TEST_F(TestPmrUnboundedVariant, assign_1_copy) { // Primitive `int` { @@ -585,7 +650,7 @@ TEST(test_unbounded_variant, assign_1_copy) // side_effect_stats stats; { - using test = TestCopyableOnly; + using test = MyCopyableOnly; using ub_var = unbounded_variant; auto side_effects = stats.make_side_effect_fn(); @@ -598,36 +663,36 @@ TEST(test_unbounded_variant, assign_1_copy) ub_var dst{}; dst = src1; - EXPECT_THAT(stats.ops, "@CCC~"); + EXPECT_THAT(stats.ops, "@CC"); EXPECT_THAT(get(src1).value_, 10); EXPECT_THAT(get(src1).payload_, 'X'); - EXPECT_THAT(get(dst).value_, 30); + EXPECT_THAT(get(dst).value_, 20); EXPECT_THAT(get(dst).payload_, 'X'); const test value2{'Z', side_effects}; - EXPECT_THAT(stats.ops, "@CCC~@"); + EXPECT_THAT(stats.ops, "@CC@"); const ub_var src2{value2}; - EXPECT_THAT(stats.ops, "@CCC~@C"); + EXPECT_THAT(stats.ops, "@CC@C"); dst = src2; - EXPECT_THAT(stats.ops, "@CCC~@CCC~C~C~~"); + EXPECT_THAT(stats.ops, "@CC@C~C"); auto dst_ptr = &dst; dst = *dst_ptr; - EXPECT_THAT(stats.ops, "@CCC~@CCC~C~C~~"); + EXPECT_THAT(stats.ops, "@CC@C~C"); EXPECT_THAT(get(src2).value_, 10); EXPECT_THAT(get(src2).payload_, 'Z'); - EXPECT_THAT(get(dst).value_, 30); + EXPECT_THAT(get(dst).value_, 20); EXPECT_THAT(get(dst).payload_, 'Z'); } EXPECT_THAT(stats.constructs, stats.destructs); - EXPECT_THAT(stats.ops, "@CCC~@CCC~C~C~~~~~~~"); + EXPECT_THAT(stats.ops, "@CC@C~C~~~~~"); } -TEST(test_unbounded_variant, assign_2_move) +TEST_F(TestPmrUnboundedVariant, assign_2_move) { // Primitive `int` { @@ -659,7 +724,7 @@ TEST(test_unbounded_variant, assign_2_move) // side_effect_stats stats; { - using test = TestMovableOnly; + using test = MyMovableOnly; using ub_var = unbounded_variant; auto side_effects = stats.make_side_effect_fn(); @@ -669,27 +734,27 @@ TEST(test_unbounded_variant, assign_2_move) ub_var dst{}; dst = std::move(src1); - EXPECT_THAT(stats.ops, "@M_M_M_"); + EXPECT_THAT(stats.ops, "@M_M_"); EXPECT_THAT(get_if(&src1), IsNull()); - EXPECT_THAT(get(dst).value_, 3); + EXPECT_THAT(get(dst).value_, 2); EXPECT_THAT(get(dst).payload_, 'X'); ub_var src2{test{'Z', side_effects}}; - EXPECT_THAT(stats.ops, "@M_M_M_@M_"); + EXPECT_THAT(stats.ops, "@M_M_@M_"); dst = std::move(src2); - EXPECT_THAT(stats.ops, "@M_M_M_@M_M_M_M_M_~"); + EXPECT_THAT(stats.ops, "@M_M_@M_~M_"); EXPECT_THAT(get_if(&src2), IsNull()); - EXPECT_THAT(get(dst).value_, 3); + EXPECT_THAT(get(dst).value_, 2); EXPECT_THAT(get(dst).payload_, 'Z'); } EXPECT_THAT(stats.constructs, stats.destructs); - EXPECT_THAT(stats.ops, "@M_M_M_@M_M_M_M_M_~~"); + EXPECT_THAT(stats.ops, "@M_M_@M_~M_~"); } -TEST(test_unbounded_variant, assign_3_move_value) +TEST_F(TestPmrUnboundedVariant, assign_3_move_value) { // Primitive `int` { @@ -703,7 +768,7 @@ TEST(test_unbounded_variant, assign_3_move_value) } } -TEST(test_unbounded_variant, make_unbounded_variant_cppref_example) +TEST_F(TestPmrUnboundedVariant, make_unbounded_variant_cppref_example) { using ub_var = unbounded_variant))>; @@ -721,7 +786,7 @@ TEST(test_unbounded_variant, make_unbounded_variant_cppref_example) EXPECT_THAT(get(a3)(), "Lambda #3.\n"); } -TEST(test_unbounded_variant, make_unbounded_variant_1) +TEST_F(TestPmrUnboundedVariant, make_unbounded_variant_1) { using ub_var = unbounded_variant; @@ -730,7 +795,7 @@ TEST(test_unbounded_variant, make_unbounded_variant_1) static_assert(std::is_same::value, ""); } -TEST(test_unbounded_variant, make_unbounded_variant_1_like) +TEST_F(TestPmrUnboundedVariant, make_unbounded_variant_1_like) { auto src = make_unbounded_variant(static_cast(42)); EXPECT_THAT(get(src), 42); @@ -740,35 +805,35 @@ TEST(test_unbounded_variant, make_unbounded_variant_1_like) ""); } -TEST(test_unbounded_variant, make_unbounded_variant_2_list) +TEST_F(TestPmrUnboundedVariant, make_unbounded_variant_2_list) { - struct TestType : rtti_helper> + struct MyType : rtti_helper> { std::size_t size_; int number_; - TestType(const std::initializer_list chars, const int number) + MyType(const std::initializer_list chars, const int number) { size_ = chars.size(); number_ = number; } }; - using ub_var = unbounded_variant; + using ub_var = unbounded_variant; - const auto src = make_unbounded_variant({'A', 'C'}, 42); - const auto& test = get(src); + const auto src = make_unbounded_variant({'A', 'C'}, 42); + const auto& test = get(src); EXPECT_THAT(test.size_, 2); EXPECT_THAT(test.number_, 42); // `cetl::unbounded_variant_like` version // - const auto dst = make_unbounded_variant({'B', 'D', 'E'}, 147); - static_assert(std::is_same>::value, ""); - EXPECT_THAT(get_if(&dst)->size_, 3); - EXPECT_THAT(get(dst).number_, 147); + const auto dst = make_unbounded_variant({'B', 'D', 'E'}, 147); + static_assert(std::is_same>::value, ""); + EXPECT_THAT(get_if(&dst)->size_, 3); + EXPECT_THAT(get(dst).number_, 147); } -TEST(test_unbounded_variant, get_cppref_example) +TEST_F(TestPmrUnboundedVariant, get_cppref_example) { using ub_var = unbounded_variant; @@ -797,7 +862,7 @@ TEST(test_unbounded_variant, get_cppref_example) EXPECT_THAT(s1, "hollo"); } -TEST(test_unbounded_variant, get_1_const) +TEST_F(TestPmrUnboundedVariant, get_1_const) { using ub_var = unbounded_variant; @@ -818,7 +883,7 @@ TEST(test_unbounded_variant, get_1_const) EXPECT_THAT(get(src), 42); } -TEST(test_unbounded_variant, get_2_non_const) +TEST_F(TestPmrUnboundedVariant, get_2_non_const) { using ub_var = unbounded_variant; @@ -854,7 +919,7 @@ TEST(test_unbounded_variant, get_2_non_const) #endif } -TEST(test_unbounded_variant, get_3_move_primitive_int) +TEST_F(TestPmrUnboundedVariant, get_3_move_primitive_int) { using ub_var = unbounded_variant; @@ -868,7 +933,7 @@ TEST(test_unbounded_variant, get_3_move_primitive_int) EXPECT_THAT(get(ub_var{42}), 42); } -TEST(test_unbounded_variant, get_3_move_empty_bad_cast) +TEST_F(TestPmrUnboundedVariant, get_3_move_empty_bad_cast) { #if defined(__cpp_exceptions) @@ -897,7 +962,7 @@ TEST(test_unbounded_variant, get_3_move_empty_bad_cast) #endif } -TEST(test_unbounded_variant, get_if_4_const_ptr) +TEST_F(TestPmrUnboundedVariant, get_if_4_const_ptr) { using ub_var = unbounded_variant; @@ -916,7 +981,7 @@ TEST(test_unbounded_variant, get_if_4_const_ptr) EXPECT_THAT(get_if(static_cast(nullptr)), IsNull()); } -TEST(test_unbounded_variant, get_if_5_non_const_ptr_with_custom_alignment) +TEST_F(TestPmrUnboundedVariant, get_if_5_non_const_ptr_with_custom_alignment) { constexpr std::size_t alignment = 4096; @@ -937,39 +1002,39 @@ TEST(test_unbounded_variant, get_if_5_non_const_ptr_with_custom_alignment) EXPECT_THAT(get_if(static_cast(nullptr)), IsNull()); } -TEST(test_unbounded_variant, get_if_polymorphic) +TEST_F(TestPmrUnboundedVariant, get_if_polymorphic) { side_effect_stats stats; { - using ub_var = unbounded_variant; + using ub_var = unbounded_variant; auto side_effects = stats.make_side_effect_fn(); - ub_var test_ubv = TestCopyableAndMovable{'Y', side_effects}; + ub_var test_ubv = MyCopyableAndMovable{'Y', side_effects}; - auto& test_base1 = get(test_ubv); + auto& test_base1 = get(test_ubv); EXPECT_THAT(test_base1.payload_, 'Y'); - EXPECT_THAT(test_base1.what(), "TestCopyableAndMovable"); - EXPECT_THAT(get_if(&test_ubv), NotNull()); - EXPECT_THAT(get_if(&test_ubv), IsNull()); - EXPECT_THAT(get_if(&test_ubv), IsNull()); + EXPECT_THAT(test_base1.what(), "MyCopyableAndMovable"); + EXPECT_THAT(get_if(&test_ubv), NotNull()); + EXPECT_THAT(get_if(&test_ubv), IsNull()); + EXPECT_THAT(get_if(&test_ubv), IsNull()); - test_ubv = TestBase{'X', side_effects}; + test_ubv = MyBase{'X', side_effects}; - auto& test_base2 = get(test_ubv); + auto& test_base2 = get(test_ubv); EXPECT_THAT(test_base2.payload_, 'X'); - EXPECT_THAT(test_base2.what(), "TestBase"); - EXPECT_THAT(get_if(&test_ubv), IsNull()); - EXPECT_THAT(get_if(&test_ubv), IsNull()); - EXPECT_THAT(get_if(&test_ubv), IsNull()); + EXPECT_THAT(test_base2.what(), "MyBase"); + EXPECT_THAT(get_if(&test_ubv), IsNull()); + EXPECT_THAT(get_if(&test_ubv), IsNull()); + EXPECT_THAT(get_if(&test_ubv), IsNull()); } EXPECT_THAT(stats.constructs, stats.destructs); - EXPECT_THAT(stats.ops, "@M_@MM_M_M_~_~"); + EXPECT_THAT(stats.ops, "@M_@~M_~"); } -TEST(test_unbounded_variant, swap_copyable) +TEST_F(TestPmrUnboundedVariant, swap_copyable) { - using test = TestCopyableOnly; + using test = MyCopyableOnly; using ub_var = unbounded_variant; ub_var empty{}; @@ -979,7 +1044,7 @@ TEST(test_unbounded_variant, swap_copyable) // Self swap a.swap(a); EXPECT_THAT(get(a).payload_, 'A'); - // EXPECT_THAT(get(&a), IsNull); //< won't compile expectedly b/c footprint is smaller + // EXPECT_THAT(get_if(&a), IsNull()); //< won't compile expectedly b/c footprint is smaller a.swap(b); EXPECT_THAT(get(a).payload_, 'B'); @@ -999,9 +1064,9 @@ TEST(test_unbounded_variant, swap_copyable) EXPECT_FALSE(another_empty.has_value()); } -TEST(test_unbounded_variant, swap_movable) +TEST_F(TestPmrUnboundedVariant, swap_movable) { - using test = TestMovableOnly; + using test = MyMovableOnly; using ub_var = unbounded_variant; ub_var empty{}; @@ -1040,66 +1105,553 @@ TEST(test_unbounded_variant, swap_movable) EXPECT_FALSE(another_empty.has_value()); } -TEST(test_unbounded_variant, emplace_1) +TEST_F(TestPmrUnboundedVariant, emplace_1) { // Primitive `char` { using ub_var = unbounded_variant; ub_var src; - src.emplace('Y'); + auto y_ptr = src.emplace('Y'); + EXPECT_THAT(get_if(&src), y_ptr); EXPECT_THAT(get(src), 'Y'); } - // `TestType` with two params ctor. + // `MyType` with two params ctor. { - struct TestType : rtti_helper> + struct MyType : rtti_helper> { char ch_; int number_; - TestType(char ch, int number) + MyType(char ch, int number) { ch_ = ch; number_ = number; } }; - using ub_var = unbounded_variant; + using ub_var = unbounded_variant; ub_var t; - t.emplace('Y', 147); - EXPECT_THAT(get(t).ch_, 'Y'); - EXPECT_THAT(get(t).number_, 147); + auto my_ptr = t.emplace('Y', 147); + EXPECT_THAT(get_if(&t), my_ptr); + EXPECT_THAT(get(t).ch_, 'Y'); + EXPECT_THAT(get(t).number_, 147); } } -TEST(test_unbounded_variant, emplace_2_initializer_list) +TEST_F(TestPmrUnboundedVariant, emplace_1_ctor_exception) { - struct TestType : rtti_helper> + side_effect_stats stats; + { + using ub_var = unbounded_variant; + + auto stats_side_effects = stats.make_side_effect_fn(); + auto throwing_side_effects = [=](side_effect_op op) { + stats_side_effects(op); + if (op == side_effect_op::Construct) + { +#if defined(__cpp_exceptions) + throw std::runtime_error("ctor"); +#endif + } + }; + + ub_var t; + EXPECT_THAT(t.has_value(), false); + EXPECT_THAT(t.valueless_by_exception(), false); + +#if defined(__cpp_exceptions) + EXPECT_THROW(sink(t.emplace('Y', throwing_side_effects)), std::runtime_error); + + EXPECT_THAT(t.has_value(), false); + EXPECT_THAT(t.valueless_by_exception(), true); + EXPECT_THAT(stats.constructs, 1); + EXPECT_THAT(stats.destructs, 0); + t.reset(); + EXPECT_THAT(stats.ops, "@"); +#else + EXPECT_THAT(t.emplace('Y', throwing_side_effects), NotNull()); + + EXPECT_THAT(t.has_value(), true); + EXPECT_THAT(t.valueless_by_exception(), false); + EXPECT_THAT(stats.constructs, 1); + EXPECT_THAT(stats.destructs, 0); + t.reset(); + EXPECT_THAT(stats.ops, "@~"); +#endif + } +} + +TEST_F(TestPmrUnboundedVariant, emplace_2_initializer_list) +{ + struct MyType : rtti_helper> { std::size_t size_; int number_; - TestType(const std::initializer_list chars, const int number) + MyType(const std::initializer_list chars, const int number) { size_ = chars.size(); number_ = number; } }; - using ub_var = unbounded_variant; + using ub_var = unbounded_variant; ub_var src; - src.emplace({'A', 'B', 'C'}, 42); + EXPECT_THAT(src.emplace({'A', 'B', 'C'}, 42), NotNull()); - const auto test = get(src); + const auto test = get(src); EXPECT_THAT(test.size_, 3); EXPECT_THAT(test.number_, 42); } +TEST_F(TestPmrUnboundedVariant, pmr_only_ctor) +{ + using ub_var = unbounded_variant<0 /*Footprint*/, true /*Copyable*/, true /*Movable*/, 1 /*Alignment*/, pmr>; + + ub_var dst{get_default_mr()}; + EXPECT_THAT(dst.has_value(), false); + + dst = ub_var{get_default_mr(), 'x'}; + EXPECT_THAT(dst.has_value(), true); + EXPECT_THAT(get(dst), 'x'); + + dst = Empty{}; + EXPECT_THAT(dst.has_value(), true); + + ub_var dst2{get_default_mr()}; + dst2 = std::move(dst); + EXPECT_THAT(dst2.has_value(), true); + + dst2 = ub_var{get_default_mr()}; + EXPECT_THAT(dst2.has_value(), false); + + const ub_var src_empty{get_mr()}; + ub_var dst3{src_empty}; + EXPECT_THAT(dst3.has_value(), false); + EXPECT_THAT(dst3.get_memory_resource(), get_mr()); + + const ub_var dst4{std::move(dst3)}; + EXPECT_THAT(dst4.has_value(), false); + EXPECT_THAT(dst4.get_memory_resource(), get_mr()); +} + +TEST_F(TestPmrUnboundedVariant, pmr_ctor_with_footprint) +{ + using ub_var = unbounded_variant<2 /*Footprint*/, true /*Copyable*/, true /*Movable*/, 2 /*Alignment*/, pmr>; + + ub_var dst{get_mr()}; + EXPECT_THAT(dst.has_value(), false); + EXPECT_THAT(dst.get_memory_resource(), get_mr()); + + dst = ub_var{get_mr(), 'x'}; + EXPECT_THAT(dst.has_value(), true); + EXPECT_THAT(get(dst), 'x'); + EXPECT_THAT(dst.get_memory_resource(), get_mr()); + + dst = Empty{}; + EXPECT_THAT(dst.has_value(), true); + EXPECT_THAT(dst.get_memory_resource(), get_mr()); + + ub_var dst2{get_default_mr()}; + EXPECT_THAT(dst2.get_memory_resource(), get_default_mr()); + dst2 = std::move(dst); + EXPECT_THAT(dst2.get_memory_resource(), get_mr()); + EXPECT_THAT(dst2.has_value(), true); + + dst2 = ub_var{get_default_mr()}; + EXPECT_THAT(dst2.has_value(), false); + dst2.reset(get_mr()); + + dst2 = std::uint16_t{0x147}; + EXPECT_THAT(dst2.has_value(), true); + EXPECT_THAT(get(dst2), 0x147); + + dst2 = int{-1}; + EXPECT_THAT(dst2.has_value(), true); + EXPECT_THAT(get(dst2), -1); + + ub_var dst3{std::move(dst2)}; + EXPECT_THAT(dst3.get_memory_resource(), get_mr()); + EXPECT_THAT(dst3.has_value(), true); + EXPECT_THAT(get(dst3), -1); + + dst3 = true; + EXPECT_THAT(dst3.has_value(), true); + EXPECT_THAT(get(dst3), true); + + const ub_var src_empty{get_mr()}; + ub_var dst4{src_empty}; + EXPECT_THAT(dst4.has_value(), false); + EXPECT_THAT(dst4.get_memory_resource(), get_mr()); + + ub_var dst5{std::move(dst4)}; + EXPECT_THAT(dst5.has_value(), false); + EXPECT_THAT(dst5.get_memory_resource(), get_mr()); + + dst5 = ub_var{get_default_mr()}; + EXPECT_THAT(dst5.has_value(), false); + EXPECT_THAT(dst5.get_memory_resource(), get_default_mr()); +} + +TEST_F(TestPmrUnboundedVariant, pmr_ctor_no_footprint) +{ + using ub_var = unbounded_variant<0 /*Footprint*/, true /*Copyable*/, true /*Movable*/, 2 /*Alignment*/, pmr>; + + ub_var dst{get_mr()}; + EXPECT_THAT(dst.has_value(), false); + EXPECT_THAT(dst.get_memory_resource(), get_mr()); + + dst = ub_var{get_mr(), 'x'}; + EXPECT_THAT(dst.has_value(), true); + EXPECT_THAT(get(dst), 'x'); + EXPECT_THAT(dst.get_memory_resource(), get_mr()); + + dst = Empty{}; + EXPECT_THAT(dst.has_value(), true); + EXPECT_THAT(dst.get_memory_resource(), get_mr()); + + ub_var dst2{get_default_mr()}; + EXPECT_THAT(dst2.get_memory_resource(), get_default_mr()); + dst2 = std::move(dst); + EXPECT_THAT(dst2.get_memory_resource(), get_mr()); + EXPECT_THAT(dst2.has_value(), true); + + dst2 = ub_var{get_default_mr()}; + EXPECT_THAT(dst2.has_value(), false); + dst2.reset(get_mr()); + + dst2 = std::uint16_t{0x147}; + EXPECT_THAT(dst2.has_value(), true); + EXPECT_THAT(get(dst2), 0x147); + + dst2 = int{-1}; + EXPECT_THAT(dst2.has_value(), true); + EXPECT_THAT(get(dst2), -1); + + ub_var dst3{std::move(dst2)}; + EXPECT_THAT(dst3.get_memory_resource(), get_mr()); + EXPECT_THAT(dst3.has_value(), true); + EXPECT_THAT(get(dst3), -1); + + dst3 = true; + EXPECT_THAT(dst3.has_value(), true); + EXPECT_THAT(get(dst3), true); + + const ub_var src_empty{get_mr()}; + ub_var dst4{src_empty}; + EXPECT_THAT(dst4.has_value(), false); + EXPECT_THAT(dst4.get_memory_resource(), get_mr()); + + ub_var dst5{std::move(dst4)}; + EXPECT_THAT(dst5.has_value(), false); + EXPECT_THAT(dst5.get_memory_resource(), get_mr()); + + dst5 = ub_var{get_default_mr()}; + EXPECT_THAT(dst5.has_value(), false); + EXPECT_THAT(dst5.get_memory_resource(), get_default_mr()); +} + +TEST_F(TestPmrUnboundedVariant, pmr_with_footprint_move_value_when_out_of_memory) +{ + using ub_var = unbounded_variant<2 /*Footprint*/, false /*Copyable*/, true /*Movable*/, 4 /*Alignment*/, pmr>; + + side_effect_stats stats; + auto side_effects = stats.make_side_effect_fn(); + + StrictMock mr_mock{}; + + ub_var dst{mr_mock.resource()}; + + // No allocations are expected (b/c we have footprint of 2). + dst = true; + dst = std::uint16_t{42}; + + // Assign a bigger (`std::uint32_t`) type value which requires more than 2 bytes. + // Emulate that there is enough memory. + { + using big_type = std::uint32_t; + + EXPECT_CALL(mr_mock, do_allocate(sizeof(big_type), 4)) + .WillOnce( + [this](std::size_t size_bytes, std::size_t alignment) { return mr_.allocate(size_bytes, alignment); }); + EXPECT_CALL(mr_mock, do_deallocate(_, sizeof(big_type), 4)) + .WillOnce([this](void* p, std::size_t size_bytes, std::size_t alignment) { + mr_.deallocate(p, size_bytes, alignment); + }); + + dst = big_type{13}; + EXPECT_THAT(dst.has_value(), true); + EXPECT_THAT(dst.valueless_by_exception(), false); + } + + // Assign even bigger (`double`) type value which requires more than 2 bytes. + // Emulate that there is no memory enough. + { + MyMovableOnly my_move_only{'X', side_effects}; + EXPECT_THAT(stats.ops, "@"); + + EXPECT_CALL(mr_mock, do_allocate(sizeof(MyMovableOnly), 4)).WillOnce(Return(nullptr)); + +#if defined(__cpp_exceptions) + EXPECT_THROW(sink(dst = std::move(my_move_only)), std::bad_alloc); +#else + dst = std::move(my_move_only); +#endif + EXPECT_THAT(dst.has_value(), false); + EXPECT_THAT(dst.valueless_by_exception(), true); + EXPECT_THAT(stats.ops, "@"); + } + EXPECT_THAT(stats.constructs, stats.destructs); + EXPECT_THAT(stats.ops, "@~"); +} + +TEST_F(TestPmrUnboundedVariant, pmr_with_footprint_copy_value_when_out_of_memory) +{ + const auto Alignment = alignof(std::max_align_t); + using ub_var = unbounded_variant<2 /*Footprint*/, true /*Copyable*/, false /*Movable*/, Alignment, pmr>; + + side_effect_stats stats; + auto side_effects = stats.make_side_effect_fn(); + + StrictMock mr_mock{}; + + ub_var dst{mr_mock.resource()}; + + // No allocations are expected (b/c we have footprint of 2). + dst = true; + dst = std::uint16_t{42}; + + // Assign a bigger (`MyCopyableOnly`) type value which requires more than 2 bytes. + // Emulate that there is enough memory. + { + const MyCopyableOnly my_copy_only{'X', side_effects}; + EXPECT_THAT(stats.ops, "@"); + + EXPECT_CALL(mr_mock, do_allocate(sizeof(MyCopyableOnly), Alignment)) + .WillOnce( + [this](std::size_t size_bytes, std::size_t alignment) { return mr_.allocate(size_bytes, alignment); }); + EXPECT_CALL(mr_mock, do_deallocate(_, sizeof(MyCopyableOnly), Alignment)) + .WillOnce([this](void* p, std::size_t size_bytes, std::size_t alignment) { + mr_.deallocate(p, size_bytes, alignment); + }); + + dst = my_copy_only; + EXPECT_THAT(stats.ops, "@C"); + + dst.reset(); + EXPECT_THAT(stats.ops, "@C~"); + } + EXPECT_THAT(stats.constructs, stats.destructs); + EXPECT_THAT(stats.ops, "@C~~"); + + // Emulate that there is no memory enough. + { + dst = true; + stats.reset(); + + MyCopyableOnly my_copy_only{'X', side_effects}; + EXPECT_THAT(stats.ops, "@"); + + EXPECT_CALL(mr_mock, do_allocate(sizeof(MyCopyableOnly), Alignment)).WillOnce(Return(nullptr)); + +#if defined(__cpp_exceptions) + EXPECT_THROW(sink(dst = my_copy_only), std::bad_alloc); +#else + dst = my_copy_only; +#endif + EXPECT_THAT(dst.has_value(), false); + EXPECT_THAT(dst.valueless_by_exception(), true); + EXPECT_THAT(stats.ops, "@"); + } + EXPECT_THAT(stats.constructs, stats.destructs); + EXPECT_THAT(stats.ops, "@~"); +} + +TEST_F(TestPmrUnboundedVariant, pmr_no_footprint_move_value_when_out_of_memory) +{ + const auto Alignment = alignof(std::max_align_t); + using ub_var = unbounded_variant<0 /*Footprint*/, false /*Copyable*/, true /*Movable*/, Alignment, pmr>; + + side_effect_stats stats; + auto side_effects = stats.make_side_effect_fn(); + + StrictMock mr_mock{}; + + ub_var dst{mr_mock.resource()}; + + // Emulate that there is no memory enough. + { + MyMovableOnly my_move_only{'X', side_effects}; + EXPECT_THAT(stats.ops, "@"); + + EXPECT_CALL(mr_mock, do_allocate(sizeof(MyMovableOnly), Alignment)).WillOnce(Return(nullptr)); + +#if defined(__cpp_exceptions) + EXPECT_THROW(sink(dst = std::move(my_move_only)), std::bad_alloc); +#else + dst = std::move(my_move_only); +#endif + EXPECT_THAT(dst.has_value(), false); + EXPECT_THAT(dst.valueless_by_exception(), true); + EXPECT_THAT(stats.ops, "@"); + } + EXPECT_THAT(stats.constructs, stats.destructs); + EXPECT_THAT(stats.ops, "@~"); +} + +TEST_F(TestPmrUnboundedVariant, pmr_no_footprint_copy_value_when_out_of_memory) +{ + const auto Alignment = alignof(std::max_align_t); + using ub_var = unbounded_variant<0 /*Footprint*/, true /*Copyable*/, false /*Movable*/, Alignment, pmr>; + + side_effect_stats stats; + auto side_effects = stats.make_side_effect_fn(); + + StrictMock mr_mock{}; + + EXPECT_CALL(mr_mock, do_allocate(sizeof(bool), Alignment)) + .WillOnce( + [this](std::size_t size_bytes, std::size_t alignment) { return mr_.allocate(size_bytes, alignment); }); + EXPECT_CALL(mr_mock, do_deallocate(_, sizeof(bool), Alignment)) + .WillOnce([this](void* p, std::size_t size_bytes, std::size_t alignment) { + mr_.deallocate(p, size_bytes, alignment); + }); + ub_var dst{mr_mock.resource(), true}; + + // Emulate that there is no memory enough. + { + MyCopyableOnly my_copy_only{'X', side_effects}; + EXPECT_THAT(stats.ops, "@"); + + EXPECT_CALL(mr_mock, do_allocate(sizeof(MyCopyableOnly), Alignment)).WillOnce(Return(nullptr)); + +#if defined(__cpp_exceptions) + EXPECT_THROW(sink(dst = my_copy_only), std::bad_alloc); +#else + dst = my_copy_only; +#endif + EXPECT_THAT(dst.has_value(), false); + EXPECT_THAT(dst.valueless_by_exception(), true); + EXPECT_THAT(stats.ops, "@"); + + dst.reset(); + EXPECT_THAT(dst.has_value(), false); + EXPECT_THAT(dst.valueless_by_exception(), false); + EXPECT_THAT(stats.ops, "@"); + } + EXPECT_THAT(stats.constructs, stats.destructs); + EXPECT_THAT(stats.ops, "@~"); +} + +TEST_F(TestPmrUnboundedVariant, pmr_swap_copyable) +{ + using test = MyCopyableOnly; + using ub_var = unbounded_variant<0, true, false, alignof(std::max_align_t), pmr>; + + ub_var empty{get_default_mr()}; + ub_var a{get_default_mr(), in_place_type_t{}, 'A'}; + ub_var b{get_default_mr(), in_place_type_t{}, 'B'}; + + // Self swap + a.swap(a); + EXPECT_THAT(get(a).payload_, 'A'); + EXPECT_THAT(get_if(&a), IsNull()); + + a.swap(b); + EXPECT_THAT(get(a).payload_, 'B'); + EXPECT_THAT(get(b).payload_, 'A'); + + empty.swap(a); + EXPECT_FALSE(a.has_value()); + EXPECT_THAT(get(empty).payload_, 'B'); + + empty.swap(a); + EXPECT_FALSE(empty.has_value()); + EXPECT_THAT(get(a).payload_, 'B'); + + ub_var another_empty{get_default_mr()}; + empty.swap(another_empty); + EXPECT_FALSE(empty.has_value()); + EXPECT_FALSE(another_empty.has_value()); +} + +TEST_F(TestPmrUnboundedVariant, pmr_swap_movable) +{ + using test = MyMovableOnly; + using ub_var = unbounded_variant; + + ub_var empty{get_mr()}; + ub_var a{get_mr(), in_place_type_t{}, 'A'}; + EXPECT_THAT(a.get_memory_resource(), get_mr()); + ub_var b{get_default_mr(), in_place_type_t{}, 'B'}; + EXPECT_THAT(b.get_memory_resource(), get_default_mr()); + + // Self swap + a.swap(a); + EXPECT_TRUE(a.has_value()); + EXPECT_FALSE(get(a).moved_); + EXPECT_THAT(get(a).payload_, 'A'); + EXPECT_THAT(a.get_memory_resource(), get_mr()); + + a.swap(b); + EXPECT_TRUE(a.has_value()); + EXPECT_TRUE(b.has_value()); + EXPECT_FALSE(get(a).moved_); + EXPECT_FALSE(get(b).moved_); + EXPECT_THAT(get(a).payload_, 'B'); + EXPECT_THAT(get(b).payload_, 'A'); + EXPECT_THAT(a.get_memory_resource(), get_default_mr()); + EXPECT_THAT(b.get_memory_resource(), get_mr()); + + empty.swap(a); + EXPECT_FALSE(a.has_value()); + EXPECT_TRUE(empty.has_value()); + EXPECT_FALSE(get(empty).moved_); + EXPECT_THAT(get(empty).payload_, 'B'); + EXPECT_THAT(a.get_memory_resource(), get_mr()); + EXPECT_THAT(empty.get_memory_resource(), get_default_mr()); + + empty.swap(a); + EXPECT_TRUE(a.has_value()); + EXPECT_FALSE(empty.has_value()); + EXPECT_FALSE(get(a).moved_); + EXPECT_THAT(get(a).payload_, 'B'); + EXPECT_THAT(empty.get_memory_resource(), get_mr()); + EXPECT_THAT(a.get_memory_resource(), get_default_mr()); + + ub_var another_empty{get_default_mr()}; + empty.swap(another_empty); + EXPECT_FALSE(empty.has_value()); + EXPECT_FALSE(another_empty.has_value()); + EXPECT_THAT(another_empty.get_memory_resource(), get_mr()); + EXPECT_THAT(empty.get_memory_resource(), get_default_mr()); + + const ub_var ub_vec{get_mr(), in_place_type_t>{}, {'A', 'B', 'C'}}; + EXPECT_THAT(ub_vec.get_memory_resource(), get_mr()); + EXPECT_THAT(get&>(ub_vec), testing::ElementsAre('A', 'B', 'C')); +} + +TEST_F(TestPmrUnboundedVariant, pmr_reset_memory_resource) +{ + using test = MyMovableOnly; + using ub_var = unbounded_variant; + + ub_var a{get_mr(), in_place_type_t{}, 'A'}; + EXPECT_TRUE(a.has_value()); + EXPECT_THAT(a.get_memory_resource(), get_mr()); + + a.reset(get_default_mr()); + EXPECT_FALSE(a.has_value()); + EXPECT_THAT(a.get_memory_resource(), get_default_mr()); +} + } // namespace namespace cetl { + template <> constexpr type_id type_id_value = {1}; @@ -1122,7 +1674,7 @@ template <> constexpr type_id type_id_value = {7}; template <> -constexpr type_id type_id_value> = +constexpr type_id type_id_value> = {0xB3, 0xB8, 0x4E, 0xC1, 0x1F, 0xE4, 0x49, 0x35, 0x9E, 0xC9, 0x1A, 0x77, 0x7B, 0x82, 0x53, 0x25}; template <> @@ -1131,6 +1683,15 @@ 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}; + } // namespace cetl // NOLINTEND(*-use-after-move) diff --git a/include/cetl/pf17/cetlpf.hpp b/include/cetl/pf17/cetlpf.hpp index 8f1f7ff6..b52fee8e 100644 --- a/include/cetl/pf17/cetlpf.hpp +++ b/include/cetl/pf17/cetlpf.hpp @@ -61,6 +61,14 @@ inline memory_resource* new_delete_resource() noexcept { return std::pmr::new_delete_resource(); } +inline memory_resource* get_default_resource() noexcept +{ + return std::pmr::get_default_resource(); +} +inline memory_resource* set_default_resource(memory_resource* mr) noexcept +{ + return std::pmr::set_default_resource(mr); +} } // namespace pmr // utility @@ -132,6 +140,14 @@ inline memory_resource* new_delete_resource() noexcept { return cetl::pf17::pmr::new_delete_resource(); } +inline memory_resource* get_default_resource() noexcept +{ + return cetl::pf17::pmr::get_default_resource(); +} +inline memory_resource* set_default_resource(memory_resource* mr) noexcept +{ + return cetl::pf17::pmr::set_default_resource(mr); +} } // namespace pmr // utility diff --git a/include/cetl/pmr/function.hpp b/include/cetl/pmr/function.hpp new file mode 100644 index 00000000..2b99bc32 --- /dev/null +++ b/include/cetl/pmr/function.hpp @@ -0,0 +1,359 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef CETL_PMR_FUNCTION_H_INCLUDED +#define CETL_PMR_FUNCTION_H_INCLUDED + +#include "cetl/unbounded_variant.hpp" + +#include +#include +#include +#include + +namespace cetl +{ +namespace pmr +{ + +template +class function; + +/// Internal implementation details. Not supposed to be used directly by the users of the library. +/// +namespace detail +{ + +// 436C9E2B-96E3-4483-9D2B-32B5147A0314 +using function_handler_typeid_t = cetl:: + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers) + type_id_type<0x43, 0x6C, 0x9E, 0x2B, 0x96, 0xE3, 0x44, 0x83, 0x9D, 0x2B, 0x32, 0xB5, 0x14, 0x7A, 0x03, 0x14>; + +/// @brief Provides an abstract interface for copyable functors. +/// +template +class function_handler : public rtti_helper +{ +public: + virtual Result operator()(Args...) = 0; + +}; // function_handler + +// DCAAADD6-BC73-4E3C-85B7-E9473641E737 +using functor_handler_typeid_t = cetl:: + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers) + type_id_type<0xDC, 0xAA, 0xAD, 0xD6, 0xBC, 0x73, 0x4E, 0x3C, 0x85, 0xB7, 0xE9, 0x47, 0x36, 0x41, 0xE7, 0x37>; + +template +class functor_handler final : public rtti_helper> +{ +public: + explicit functor_handler(const Functor& functor) + : functor_{functor} + { + } + + explicit functor_handler(Functor&& functor) + : functor_{std::move(functor)} + { + } + + Result operator()(Args... args) override + { + return functor_(args...); + } + +private: + Functor functor_; + +}; // functor_handler + +[[noreturn]] inline void throw_bad_function_call() +{ +#if defined(__cpp_exceptions) + throw std::bad_function_call{}; +#else + std::terminate(); +#endif +} + +} // namespace detail + +/// @brief A polymorphic function wrapper. +/// +/// The class is similar to `std::function` but with the following differences: +/// - it allows to set a small object optimization footprint; +/// - it doesn't use c++ heap; if needed PMR-enabled version can be used to manage memory. +/// +template +class function final +{ + template >, function>::value) && + // TODO: Uncomment when we have `cetl::is_invocable_v`. + true /* std::is_invocable_v */> + struct IsCallable; + template + struct IsCallable + { + static constexpr bool value = false; + }; + template + struct IsCallable + { + static constexpr bool value = + std::is_void::value || + // TODO: Uncomment when we have `cetl::invoke_result_t`. + true /* std::is_convertible, Result>::value */; + }; + template + using EnableIfLvIsCallable = std::enable_if_t::value>; + +public: + using result_type = Result; + + /// @brief Creates an empty function. + /// + template > + function() noexcept + { + } + + /// @brief Destroys the std::function instance. If the std::function is not empty, its target is destroyed also. + /// + ~function() = default; + + /// @brief Creates an empty function with specific memory resource. + /// + template > + explicit function(Pmr* const mem_res) + : any_handler_{mem_res} + { + CETL_DEBUG_ASSERT(nullptr != mem_res, ""); + } + + /// @brief Copies the target of `other` to the target of `*this`. + /// + /// If `other` is empty, `*this` will be empty right after the call too. + /// For PMR-enabled functions, the memory resource is copied as well. + /// + /// Any failure during the copy operation will result in the "valueless by exception" state. + /// + function(const function& other) + : any_handler_{other.any_handler_} + , handler_ptr_{get_if(&any_handler_)} + { + } + + /// @brief Moves the target of `other` to the target of `*this`. + /// + /// If other is empty, *this will be empty right after the call too. + /// `other` is in a valid but unspecified state right after the call. + /// For PMR-enabled functions, the memory resource is copied as well. + /// + /// Any failure during the move operation will result in the "valueless by exception" state. + /// + function(function&& other) noexcept + : any_handler_{std::move(other.any_handler_)} + , handler_ptr_{get_if(&any_handler_)} + { + other.handler_ptr_ = nullptr; + } + + /// @brief Initializes the target with `std::forward(functor)`. + /// + /// Any failure during the construction will result in the "valueless by exception" state. + /// + template , + typename = EnableIfLvIsCallable, + typename PmrAlias = Pmr, + typename = cetl::detail::EnableIfNotPmrT> + function(Functor&& functor) + : any_handler_{detail::functor_handler{std::forward(functor)}} + , handler_ptr_{get_if(&any_handler_)} + { + } + + /// @brief Initializes the target with `std::forward(functor)` and specific memory resource. + /// + /// Any failure during the construction will result in the "valueless by exception" state. + /// + template , + typename = EnableIfLvIsCallable, + typename PmrAlias = Pmr, + typename = cetl::detail::EnableIfPmrT> + function(Pmr* const mem_res, Functor&& functor) + : any_handler_{mem_res, detail::functor_handler{std::forward(functor)}} + , handler_ptr_{get_if>(&any_handler_)} + { + } + + /// @brief Sets the target with `std::forward(functor)` + /// + /// Any failure during the set operations will result in the "valueless by exception" state. + /// + template , + typename = EnableIfLvIsCallable> + function& operator=(Functor&& functor) + { + any_handler_ = detail::functor_handler{std::forward(functor)}; + handler_ptr_ = get_if(&any_handler_); + return *this; + } + + /// @brief Assigns a copy of target of `other`. + /// + /// Any failure during the assignment will result in the "valueless by exception" state. + /// + function& operator=(const function& other) + { + if (this != &other) + { + any_handler_ = other.any_handler_; + handler_ptr_ = get_if(&any_handler_); + } + return *this; + } + + /// @brief Moves the target of `other` to `*this` + /// + /// Any failure during the movement will result in the "valueless by exception" state. + /// + function& operator=(function&& other) noexcept + { + if (this != &other) + { + any_handler_ = std::move(other.any_handler_); + handler_ptr_ = get_if(&any_handler_); + other.handler_ptr_ = nullptr; + } + return *this; + } + + /// @brief Drops the current target. `*this` is empty after the call. + /// + function& operator=(std::nullptr_t) noexcept + { + reset(); + return *this; + } + + /// @brief Checks whether `*this` stores a callable function target, i.e. is not empty. + /// + explicit operator bool() const noexcept + { + CETL_DEBUG_ASSERT(any_handler_.has_value() == (nullptr != handler_ptr_), ""); + + return any_handler_.has_value(); + } + + /// @brief Invokes the stored callable function target with the parameters args. + /// + /// @param args Arguments to pass to the stored callable function target. + /// @return None if `Result` is `void`. Otherwise the return value of the invocation of the stored callable object. + /// @throws `std::bad_function_call` if `*this` does not store a callable function target, i.e. `!*this == true`. + /// + Result operator()(Args... args) const + { + if (!any_handler_.has_value()) + { + detail::throw_bad_function_call(); + } + + CETL_DEBUG_ASSERT(nullptr != handler_ptr_, ""); + + return handler_ptr_->operator()(args...); + } + + /// @brief Resets the current target. `*this` is empty after the call. + /// + void reset() noexcept + { + any_handler_.reset(); + handler_ptr_ = nullptr; + } + + /// @brief Resets the current target, and sets specific memory resource. `*this` is empty after the call. + /// + template > + void reset(Pmr* const mem_res) noexcept + { + any_handler_.reset(mem_res); + handler_ptr_ = nullptr; + } + + /// @brief Swaps the contents of `*this` and `other`. + /// + /// Any failure during the swap operation could result in the "valueless by exception" state, + /// and depending on which stage of swapping the failure happened + /// it could affect (invalidate) either of `*this` or `other` function. + /// Use `valueless_by_exception()` method to check if a function is in such failure state, + /// and `reset` (or `reset(Pmr*)`) method (or assign a new value) to recover from it. + /// + void swap(function& other) noexcept + { + any_handler_.swap(other.any_handler_); + + handler_ptr_ = get_if(&any_handler_); + other.handler_ptr_ = get_if(&other.any_handler_); + } + + /// True if the function is valueless b/c of an exception or OOM. + /// + /// Use `reset` method (or try assign a new value) to recover from this state. + /// + CETL_NODISCARD bool valueless_by_exception() const noexcept + { + return any_handler_.valueless_by_exception(); + } + + /// @brief Gets current memory resource in use by the function. + /// + template > + CETL_NODISCARD Pmr* get_memory_resource() const noexcept + { + return any_handler_.get_memory_resource(); + } + +private: + using handler_t = detail::function_handler; + // TODO: Invest (later) into exploiting `Copyable` & `Movable` template params of our `unbounded_variant` - + // maybe we can make our `function` itself `Copyable` & `Movable` parametrized (aka + // `std::move_only_function`). + using any_handler_t = unbounded_variant; + + any_handler_t any_handler_; + handler_t* handler_ptr_{nullptr}; + +}; // function + +} // namespace pmr +} // namespace cetl + +namespace std +{ + +/// @brief Overloads the `std::swap` algorithm for `cetl::pmr::function`. +/// Exchanges the state of `lhs` with that of `rhs`. Effectively calls `lhs.swap(rhs)`. +/// +/// Any failure during the swap operation could result in the "valueless by exception" state, +/// and depending on which stage of swapping the failure happened +/// it could affect (invalidate) either of `lhs` or `rhs` function. +/// Use `valueless_by_exception()` method to check if a function is in such failure state, +/// and `reset` (or `reset(Pmr*)`) method (or assign a new value) to recover from it. +/// +template +void swap(cetl::pmr::function& lhs, + cetl::pmr::function& rhs) noexcept +{ + lhs.swap(rhs); +} + +} // namespace std + +#endif // CETL_PMR_FUNCTION_H_INCLUDED diff --git a/include/cetl/pmr/interface_ptr.hpp b/include/cetl/pmr/interface_ptr.hpp new file mode 100644 index 00000000..d39d93d3 --- /dev/null +++ b/include/cetl/pmr/interface_ptr.hpp @@ -0,0 +1,206 @@ +/// @copyright +/// Copyright (C) OpenCyphal Development Team +/// Copyright Amazon.com Inc. or its affiliates. +/// SPDX-License-Identifier: MIT + +#ifndef CETL_PMR_INTERFACE_PTR_H_INCLUDED +#define CETL_PMR_INTERFACE_PTR_H_INCLUDED + +#include "cetl/pmr/function.hpp" + +#include +#include + +namespace cetl +{ +namespace pmr +{ + +/// RAII helper for cetl::pf17::pmr::polymorphic_allocator and std::pmr::polymorphic_allocator. +/// Use with cetl::pmr::InterfaceFactory for the best and safest +/// experience. Remember, be safe, use the cetl::pmr::InterfaceFactory. +/// +/// @note +/// See cetl::pmr::InterfaceFactory for an example of how to use this type. +/// +/// @tparam Interface The interface type of the polymorphic allocator to use for deallocation. +/// +template +class PmrInterfaceDeleter final +{ +public: + /// Constructs a Concrete type-erased deleter for the given interface type. + /// + /// @tparam PmrAllocator The type of the polymorphic allocator to use for deallocation. + /// + template + PmrInterfaceDeleter(PmrAllocator alloc, std::size_t obj_count) + : deleter_{[alloc, obj_count](Interface* ptr) mutable { + using Concrete = typename PmrAllocator::value_type; + + auto* concrete_ptr = static_cast(ptr); + concrete_ptr->~Concrete(); + alloc.deallocate(concrete_ptr, obj_count); + }} + { + } + + /// Functor called by smart-pointer to deallocate and deconstruct objects. + /// + /// @param ptr The object to deconstruct and deallocate. + /// + void operator()(Interface* ptr) noexcept + { + deleter_(ptr); + } + + // Below convertor constructor is only possible with enabled PMR at `function`. + // For now, we decided to comment it out, so that `InterfacePtr` always stays + // within 24-bytes small object optimization, namely without extra memory allocation + // just for the sake of "advanced" deleter (actually chain of casters down to original `Concrete` pointer). + // + // NOTE: It is possible to avoid PMR if we define a certain static limit on the maximum number of + // down-conversions performed on an interface ptr. One issue is that exceeding that limit is a + // runtime error, so it will have to be handled correctly somehow. It could be that std::abort() + // is a sensible choice here, but probably not. The way to implement this solution is to keep a + // stack of the following convertors, where each convertor reverses one up-conversion: + // + // using caster_t = void* (*) (void*); + // /// Creates a function that performs a safe type conversion on a type-erased void* pointer. + // template + // static caster_t make_caster() noexcept + // { return [](void* const ptr) -> void* { return static_cast(static_cast(ptr)); }; } + // + // template ::value>> + // PmrInterfaceDeleter(const PmrInterfaceDeleter& other) + // : deleter_{other.get_memory_resource(), [other](Interface* ptr) { + // // Delegate to the down class deleter. + // // The down-casting is assumed to be safe because the caller + // // guarantees that *ptr is of type Down. + // // This is a possible deviation from AUTOSAR M5-2-3; whether the + // // type is polymorphic or not is irrelevant in this context. + // other.deleter_(static_cast(ptr)); + // }} + // { + // } + // + // Pmr* get_memory_resource() const noexcept + // { + // return deleter_.get_memory_resource(); + // } + // + // private: + // template + // friend class PmrInterfaceDeleter; + +private: + cetl::pmr::function deleter_; + +}; // PmrInterfaceDeleter + +template +using InterfacePtr = std::unique_ptr>; + +/// Interface Factory helper for creating objects with polymorphic allocators using proper RAII semantics. +/// Uses the cetl::pmr::PmrInterfaceDeleter type to ensure proper type-erased deallocation. +/// +/// Example usage: +/// +/// @snippet{trimleft} example_07_polymorphic_alloc_deleter.cpp example_usage_2 +/// (@ref example_07_polymorphic_alloc_deleter "See full example here...") +/// +class InterfaceFactory final +{ +public: + ~InterfaceFactory() = delete; + InterfaceFactory() = delete; + + template + CETL_NODISCARD static InterfacePtr make_unique(PmrAllocator alloc, Args&&... args) + { + // Allocate memory for the concrete object. + // Then try to construct it in-place - it could potentially throw, + // so RAII will deallocate the memory BUT won't try to destroy the uninitialized object! + // + ConcreteRaii concrete_raii{alloc}; + if (auto* const concrete_ptr = concrete_raii.get()) + { + concrete_raii.construct(std::forward(args)...); + } + + // Everything is good, so now we can move ownership of the concrete object to the interface smart pointer. + // + return InterfacePtr{concrete_raii.release(), PmrInterfaceDeleter{alloc, 1}}; + } + +private: + /// Helper RAII class for temporal management of allocated/initialized memory of a Concrete object. + /// In use together with `InterfacePtr` to ensure proper deallocation in case of exceptions. + /// + template + class ConcreteRaii final + { + using Concrete = typename PmrAllocator::value_type; + + public: + ConcreteRaii(PmrAllocator& pmr_allocator) + : concrete_{pmr_allocator.allocate(1)} + , constructed_{false} + , pmr_allocator_{pmr_allocator} + { + } + ~ConcreteRaii() + { + if (nullptr != concrete_) + { + if (constructed_) + { + concrete_->~Concrete(); + } + + pmr_allocator_.deallocate(concrete_, 1); + } + } + ConcreteRaii(const ConcreteRaii&) = delete; + ConcreteRaii(ConcreteRaii&&) noexcept = delete; + ConcreteRaii& operator=(const ConcreteRaii&) = delete; + ConcreteRaii& operator=(ConcreteRaii&&) noexcept = delete; + + template + void construct(Args&&... args) + { + CETL_DEBUG_ASSERT(!constructed_, ""); + + if (nullptr != concrete_) + { + pmr_allocator_.construct(concrete_, std::forward(args)...); + constructed_ = true; + } + } + + Concrete* get() const noexcept + { + return concrete_; + } + + Concrete* release() + { + constructed_ = false; + Concrete* result{nullptr}; + std::swap(result, concrete_); + return result; + } + + private: + Concrete* concrete_; + bool constructed_; + PmrAllocator& pmr_allocator_; + + }; // ConcreteRaii + +}; // InterfaceFactory + +} // namespace pmr +} // namespace cetl + +#endif // CETL_PMR_INTERFACE_PTR_H_INCLUDED diff --git a/include/cetl/unbounded_variant.hpp b/include/cetl/unbounded_variant.hpp index 1f712f6d..876dea45 100644 --- a/include/cetl/unbounded_variant.hpp +++ b/include/cetl/unbounded_variant.hpp @@ -16,10 +16,6 @@ #include #include -#ifndef CETL_H_ERASE -# include "cetl/cetl.hpp" -#endif - namespace cetl { @@ -48,36 +44,502 @@ class bad_unbounded_variant_access : public std::bad_cast #endif // defined(__cpp_exceptions) || defined(CETL_DOXYGEN) // Forward declarations -template +template class unbounded_variant; namespace detail { +inline std::nullptr_t throw_bad_alloc() +{ +#if defined(__cpp_exceptions) + throw std::bad_alloc(); +#else + return nullptr; +#endif +} + +[[noreturn]] inline void throw_bad_unbounded_variant_access() +{ +#if defined(__cpp_exceptions) + throw bad_unbounded_variant_access(); +#else + CETL_DEBUG_ASSERT(false, "bad_unbounded_variant_access"); + std::terminate(); +#endif +} + +template +using IsPmr = std::integral_constant::value>; + +template +using EnableIfPmrT = std::enable_if_t::value>; + +template +using EnableIfNotPmrT = std::enable_if_t::value>; + +// MARK: - Storage policy. +// +template +struct base_storage; +// +// Invalid non-PMR and zero footprint case. +template +struct base_storage +{ + template + void check_footprint() const noexcept + { + // Non-PMR storage can store any value as long as it fits into the footprint, + // which is not possible with zero footprint. + static_assert(sizeof(Tp) <= 0, "Make non-zero footprint, or enable PMR support."); + } +}; +// In-Place only case (no PMR at all). template -struct base_storage // NOLINT(*-pro-type-member-init) +struct base_storage +{ + template + void check_footprint() const noexcept + { + // Non-PMR storage can store any value as long as it fits into the footprint. + static_assert(sizeof(Tp) <= Footprint, "Enlarge the footprint"); + } + + /// @brief Allocates enough raw storage for a target value. + /// + /// @param size_bytes Size of the target type in bytes. + /// @return Pointer to the storage (never `nullptr` for this specialization). + /// + CETL_NODISCARD void* alloc_new_raw_storage(const std::size_t size_bytes) noexcept + { + CETL_DEBUG_ASSERT(0UL < size_bytes, ""); + CETL_DEBUG_ASSERT(size_bytes <= Footprint, ""); + CETL_DEBUG_ASSERT(0UL == value_size_, "This object is expected to be a brand new one."); + + // We need to store (presumably) allocated size b/c this is important + // for detection of the "valueless by exception" state in case something goes wrong later + // (like in-place value construction failure) - in such failure state + // there will `>0` value size BUT `nullptr` handlers (f.e. `value_destroyer_`). + // + value_size_ = size_bytes; + + return inplace_buffer_; + } + + CETL_NODISCARD bool copy_raw_storage_from(const base_storage& src) noexcept + { + CETL_DEBUG_ASSERT(0UL == value_size_, "This object is expected to be a brand new (or reset) one."); + + value_size_ = src.value_size_; + + return value_size_ > 0UL; + } + + CETL_NODISCARD bool move_raw_storage_from(base_storage& src) noexcept + { + CETL_DEBUG_ASSERT(0UL == value_size_, "This object is expected to be a brand new (or reset) one."); + + value_size_ = src.value_size_; + + return value_size_ > 0UL; + } + + void reset() noexcept + { + value_size_ = 0UL; + } + + CETL_NODISCARD std::size_t get_value_size() const noexcept + { + return value_size_; + } + + CETL_NODISCARD constexpr void* get_raw_storage() noexcept + { + CETL_DEBUG_ASSERT(0UL != value_size_, ""); + + return inplace_buffer_; + } + + CETL_NODISCARD constexpr const void* get_raw_storage() const noexcept + { + CETL_DEBUG_ASSERT(0UL != value_size_, ""); + + return inplace_buffer_; + } + +private: + // We need to align `inplace_buffer_` to the given value (maximum alignment by default). + // Also, we need to ensure that the buffer is at least 1 byte long. + // NB! It's intentional and by design that the `inplace_buffer_` is the very first member of `unbounded_variant` + // memory layout. In such way pointer to a `unbounded_variant` and its stored value are the same - + // could be useful during debugging/troubleshooting. + alignas(Alignment) std::uint8_t inplace_buffer_[std::max(Footprint, 1UL)]; + + // Stores the size of the allocated space in the buffer for a value. + // The size could be less than `Footprint`, but never zero except when variant is empty (valid and valueless). + std::size_t value_size_{0UL}; +}; +// PMR only case (zero in-place footprint). +template +struct base_storage { - base_storage() = default; + base_storage() + : value_size_{0UL} + , allocated_buffer_{nullptr} + , mem_res_{nullptr} + { + } + + explicit base_storage(Pmr* const mem_res) + : value_size_{0UL} + , allocated_buffer_{nullptr} + , mem_res_{mem_res} + { + CETL_DEBUG_ASSERT(nullptr != mem_res_, ""); + } + + template + void check_footprint() const noexcept + { + // PMR-based storage can store any size of the value. + } + + CETL_NODISCARD Pmr* get_memory_resource() const noexcept + { + CETL_DEBUG_ASSERT(nullptr != mem_res_, ""); + return mem_res_; + } + + Pmr* set_memory_resource(Pmr* const mem_res) noexcept + { + CETL_DEBUG_ASSERT(nullptr != mem_res, ""); + CETL_DEBUG_ASSERT(nullptr != mem_res_, ""); + + Pmr* tmp = mem_res_; + mem_res_ = mem_res; + return tmp; + } + + /// @brief Allocates enough raw storage for a target value. + /// + /// The storage is always attempted to be allocated from PMR. If it fails then this object + /// will be in the valueless by exception state (see `valueless_by_exception()` method). + /// + /// @param size_bytes Size of the type in bytes. + /// @return Pointer to the successfully allocated buffer. + /// Could be `nullptr` if PMR allocation fails and c++ exceptions are disabled. + /// @throws `std::bad_alloc` if PMR allocation fails and c++ exceptions are enabled. + /// + CETL_NODISCARD void* alloc_new_raw_storage(const std::size_t size_bytes) + { + CETL_DEBUG_ASSERT(0UL < size_bytes, ""); + CETL_DEBUG_ASSERT((0UL == value_size_) && (nullptr == allocated_buffer_), + "This object is expected to be a brand new one."); + + // We need to store (presumably) allocated size, regardless of whether actual allocation below succeeded. + // This is important for detection of the "valueless by exception" state in case something goes wrong later + // (like the PRM allocation below, or a value construction failure even later) - in such failure state + // there will `>0` value size BUT `nullptr` handlers (f.e. `value_destroyer_`). + // + value_size_ = size_bytes; + + allocated_buffer_ = static_cast(get_memory_resource()->allocate(size_bytes, Alignment)); + if (nullptr == allocated_buffer_) + { + return throw_bad_alloc(); + } + + return allocated_buffer_; + } + + CETL_NODISCARD bool copy_raw_storage_from(const base_storage& src) + { + CETL_DEBUG_ASSERT((0UL == value_size_) && (nullptr == allocated_buffer_), + "This object is expected to be a brand new (or reset) one."); + + mem_res_ = src.get_memory_resource(); + + // We don't allocate buffer for the valueless variant. + if (src.value_size_ == 0UL) + { + // Later we don't need to call copy-ctor for the valueless variant. + return false; + } + + return alloc_new_raw_storage(src.value_size_) != nullptr; + } + + /// @brief Moves source memory resource and its possible allocated buffer to the destination. + /// + /// This is actually a value "move" operation, so there will be no move-ctor call later. + /// + /// @return `false` indicating that the value has been moved already (as part of allocated buffer borrowing). + /// + CETL_NODISCARD bool move_raw_storage_from(base_storage& src) noexcept + { + CETL_DEBUG_ASSERT((0UL == value_size_) && (nullptr == allocated_buffer_), + "This object is expected to be a brand new (or reset) one."); + + mem_res_ = src.get_memory_resource(); + value_size_ = src.value_size_; + allocated_buffer_ = src.allocated_buffer_; + + // We've borrowed the buffer from the source. + if (allocated_buffer_ != nullptr) + { + src.value_size_ = 0UL; + src.allocated_buffer_ = nullptr; + } + + // B/c there is no in-place storage (Footprint == 0), + // we always move previously allocated buffer along with its value. + return false; + } + + void reset() noexcept + { + CETL_DEBUG_ASSERT((nullptr == allocated_buffer_) || (0UL != value_size_), ""); + + if (nullptr != allocated_buffer_) + { + get_memory_resource()->deallocate(allocated_buffer_, value_size_, Alignment); + allocated_buffer_ = nullptr; + } + + value_size_ = 0UL; + } + + CETL_NODISCARD std::size_t get_value_size() const noexcept + { + return value_size_; + } CETL_NODISCARD void* get_raw_storage() noexcept { - return static_cast(buffer_); + CETL_DEBUG_ASSERT(0UL != value_size_, ""); + CETL_DEBUG_ASSERT(nullptr != allocated_buffer_, ""); + + return allocated_buffer_; } CETL_NODISCARD const void* get_raw_storage() const noexcept { - return static_cast(buffer_); + CETL_DEBUG_ASSERT(0UL != value_size_, ""); + CETL_DEBUG_ASSERT(nullptr != allocated_buffer_, ""); + + return allocated_buffer_; + } + +private: + // Stores the size of the allocated buffer for a value. + std::size_t value_size_; + std::uint8_t* allocated_buffer_; + Pmr* mem_res_; +}; +// Both PMR & In-Place case. +template +struct base_storage +{ + base_storage() + : value_size_{0UL} + , allocated_buffer_{nullptr} + , mem_res_{nullptr} + { + } + + explicit base_storage(Pmr* const mem_res) + : value_size_{0UL} + , allocated_buffer_{nullptr} + , mem_res_{mem_res} + { + CETL_DEBUG_ASSERT(nullptr != mem_res_, ""); + } + + template + void check_footprint() const noexcept + { + // PMR-based storage can store any size of the value. + } + + CETL_NODISCARD Pmr* get_memory_resource() const noexcept + { + CETL_DEBUG_ASSERT(nullptr != mem_res_, ""); + return mem_res_; + } + + Pmr* set_memory_resource(Pmr* const mem_res) noexcept + { + CETL_DEBUG_ASSERT(nullptr != mem_res, ""); + CETL_DEBUG_ASSERT(nullptr != mem_res_, ""); + + Pmr* tmp = mem_res_; + mem_res_ = mem_res; + return tmp; + } + + /// @brief Allocates enough raw storage for a target type value. + /// + /// The storage is either in-place buffer (if it fits), or attempted to be allocated from PMR. If it fails + /// then this object will be in the valueless by exception state (see `valueless_by_exception()` method). + /// + /// @param size_bytes Size of the type in bytes. + /// @return Pointer to the buffer (either in-place or allocated one). + /// Could be `nullptr` if PMR allocation fails and c++ exceptions are disabled. + /// @throws `std::bad_alloc` if PMR allocation fails and c++ exceptions are enabled. + /// + CETL_NODISCARD void* alloc_new_raw_storage(const std::size_t size_bytes) + { + CETL_DEBUG_ASSERT(0UL < size_bytes, ""); + CETL_DEBUG_ASSERT((0UL == value_size_) && (nullptr == allocated_buffer_), + "This object is expected to be a brand new one."); + + // Regardless of the path (in-place or allocated buffer), we need to store (presumably) allocated size. + // This is important for detection of the "valueless by exception" state in case something goes wrong later + // (like possible PRM allocation below, or a value construction failure even later) - + // in such failure state there will `>0` value size BUT `nullptr` handlers (f.e. `value_destroyer_`). + // + value_size_ = size_bytes; + + if (size_bytes <= Footprint) + { + return inplace_buffer_; + } + + allocated_buffer_ = static_cast(get_memory_resource()->allocate(size_bytes, Alignment)); + if (nullptr == allocated_buffer_) + { + return throw_bad_alloc(); + } + + return allocated_buffer_; + } + + CETL_NODISCARD bool copy_raw_storage_from(const base_storage& src) + { + CETL_DEBUG_ASSERT((0UL == value_size_) && (nullptr == allocated_buffer_), + "This object is expected to be a brand new (or reset) one."); + + mem_res_ = src.get_memory_resource(); + + // We don't even try to allocate buffer for the valueless variant. + if (src.value_size_ == 0UL) + { + // Later we don't need to call copy-ctor for the valueless variant. + return false; + } + + return alloc_new_raw_storage(src.value_size_) != nullptr; + } + + /// @brief Moves source memory resource and its possible allocated buffer to the destination. + /// + /// If source value was stored in the allocated buffer (in contrast to in-place buffer), + /// the value will be moved as well, so there will be no move-ctor call later. + /// + /// @return `false` indicating that the value has been moved already (as part of allocated buffer borrowing). + /// Otherwise, move-ctor call has to be performed as well (by the caller). + /// + CETL_NODISCARD bool move_raw_storage_from(base_storage& src) noexcept + { + mem_res_ = src.mem_res_; + value_size_ = src.value_size_; + allocated_buffer_ = src.allocated_buffer_; + + // We've borrowed the buffer from the source. + if (nullptr != allocated_buffer_) + { + src.value_size_ = 0UL; + src.allocated_buffer_ = nullptr; + } + + // Only borrowing of previously allocated buffer moves the value as well. + // In contrast, in-place buffer value still remains in the source. + return (value_size_ > 0UL) && (nullptr == allocated_buffer_); + } + + void reset() noexcept + { + if (nullptr != allocated_buffer_) + { + get_memory_resource()->deallocate(allocated_buffer_, value_size_, Alignment); + allocated_buffer_ = nullptr; + } + + value_size_ = 0UL; + } + + CETL_NODISCARD std::size_t get_value_size() const noexcept + { + return value_size_; + } + + CETL_NODISCARD void* get_raw_storage() noexcept + { + CETL_DEBUG_ASSERT(0UL != value_size_, ""); + CETL_DEBUG_ASSERT((nullptr == allocated_buffer_) || (Footprint < value_size_), ""); + + return (nullptr != allocated_buffer_) ? allocated_buffer_ : inplace_buffer_; + } + + CETL_NODISCARD const void* get_raw_storage() const noexcept + { + CETL_DEBUG_ASSERT(0UL != value_size_, ""); + CETL_DEBUG_ASSERT((nullptr == allocated_buffer_) || (Footprint < value_size_), ""); + + return (nullptr != allocated_buffer_) ? allocated_buffer_ : inplace_buffer_; + } + +private: + // We need to align `inplace_buffer_` to the given value (maximum alignment by default). + // NB! It's intentional and by design that the `inplace_buffer_` is the very first member of `unbounded_variant` + // memory layout. In such way pointer to a `unbounded_variant` and its stored value are the same + // in case of small object optimization - could be useful during debugging/troubleshooting. + alignas(Alignment) std::uint8_t inplace_buffer_[Footprint]; + + // Stores the size of the allocated space in a buffer for a value. + // The size could be either `<=Footprint` (in-place) or `>Footprint (PMR allocated), + // but never zero except when variant is empty (valid and valueless). + std::size_t value_size_; + std::uint8_t* allocated_buffer_; + Pmr* mem_res_; + +}; // base_storage + +// MARK: - Access policy. +// +template +struct base_access : base_storage +{ + base_access() = default; + + template > + explicit base_access(Pmr* const mem_res) + : base{mem_res} + { } CETL_NODISCARD bool has_value() const noexcept { - return nullptr != value_destroyer_; + return (base::get_value_size() > 0UL) && (nullptr != value_destroyer_); + } + + /// True if the variant is valueless b/c of an exception or OOM. + /// + /// Use `reset` method (or try assign a new value) to recover from this state. + /// + CETL_NODISCARD bool valueless_by_exception() const noexcept + { + // Missing destroyer is the sign that there was an attempt store a value (and hence value size is >0), + // but something went wrong along the way (like PMR allocation or value construction failure). + return (base::get_value_size() > 0UL) && (nullptr == value_destroyer_); } template void make_handlers() noexcept { - static_assert(sizeof(Tp) <= Footprint, "Enlarge the footprint"); + base::template check_footprint(); CETL_DEBUG_ASSERT(nullptr == value_destroyer_, "Expected to be empty before making handlers."); CETL_DEBUG_ASSERT(nullptr == value_converter_, ""); @@ -118,8 +580,7 @@ struct base_storage // NOLINT(*-pro-type-member-init) template CETL_NODISCARD void* get_ptr() noexcept { - static_assert(sizeof(ValueType) <= Footprint, - "Cannot contain the requested type since the footprint is too small"); + base::template check_footprint(); if (!has_value()) { @@ -127,14 +588,13 @@ struct base_storage // NOLINT(*-pro-type-member-init) } CETL_DEBUG_ASSERT(nullptr != value_const_converter_, "Non-empty storage is expected to have value converter."); - return value_converter_(get_raw_storage(), type_id_value); + return value_converter_(base::get_raw_storage(), type_id_value); } template CETL_NODISCARD const void* get_ptr() const noexcept { - static_assert(sizeof(ValueType) <= Footprint, - "Cannot contain the requested type since the footprint is too small"); + base::template check_footprint(); if (!has_value()) { @@ -142,35 +602,44 @@ struct base_storage // NOLINT(*-pro-type-member-init) } CETL_DEBUG_ASSERT(nullptr != value_const_converter_, "Non-empty storage is expected to have value converter."); - return value_const_converter_(get_raw_storage(), type_id_value); + return value_const_converter_(base::get_raw_storage(), type_id_value); } - void copy_handlers_from(const base_storage& src) noexcept + void copy_handlers_from(const base_access& src) noexcept { value_destroyer_ = src.value_destroyer_; value_converter_ = src.value_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_const_converter_ = src.value_const_converter_; + + src.reset(); + } + void reset() noexcept { - if (value_destroyer_) + // Non-PMR storage can store any value as long as it fits into the footprint. + static_assert((Footprint > 0) || IsPmr::value, "Make non-zero footprint, or enable PMR support."); + + if (has_value()) { - value_destroyer_(get_raw_storage()); - value_destroyer_ = nullptr; + value_destroyer_(base::get_raw_storage()); } + value_destroyer_ = nullptr; value_converter_ = nullptr; value_const_converter_ = nullptr; + + base::reset(); } private: - // We need to align the buffer to the given value (maximum alignment by default). - // Also, we need to ensure that the buffer is at least 1 byte long. - // NB! It's intentional and by design that the `buffer_` is the very first member of `unbounded_variant` - // memory layout. In such way pointer to a `unbounded_variant` and its stored value are the same - - // could be useful during debugging/troubleshooting. - alignas(Alignment) char buffer_[std::max(Footprint, 1UL)]; + using base = base_storage; // Holds type-erased value destroyer. `nullptr` if storage has no value stored. void (*value_destroyer_)(void* self) = nullptr; @@ -180,27 +649,52 @@ struct base_storage // NOLINT(*-pro-type-member-init) void* (*value_converter_)(void* self, const type_id& id) = nullptr; const void* (*value_const_converter_)(const void* self, const type_id& id) = nullptr; -}; // base_storage +}; // base_access -template +// MARK: - Handlers policy. +// +template struct base_handlers; // -template -struct base_handlers : base_storage -{}; +template +struct base_handlers + : base_access +{ + base_handlers() = default; + + template > + explicit base_handlers(Pmr* const mem_res) + : base{mem_res} + { + } + +private: + using base = base_access; +}; // -template -struct base_handlers : base_storage +template +struct base_handlers + : base_access { + base_handlers() = default; + + template > + explicit base_handlers(Pmr* const mem_res) + : base{mem_res} + { + } + void copy_handlers_from(const base_handlers& src) noexcept { - base::copy_handlers_from(src); value_copier_ = src.value_copier_; + + base::copy_handlers_from(src); } void reset() noexcept { value_copier_ = nullptr; + base::reset(); } @@ -208,16 +702,27 @@ struct base_handlers : base_storage; + using base = base_access; }; // -template -struct base_handlers : base_storage +template +struct base_handlers + : base_access { - void copy_handlers_from(const base_handlers& src) noexcept + base_handlers() = default; + + template > + explicit base_handlers(Pmr* const mem_res) + : base{mem_res} { - base::copy_handlers_from(src); - value_mover_ = src.value_mover_; + } + + void move_handlers_from(base_handlers& src) noexcept + { + value_mover_ = src.value_mover_; + src.value_mover_ = nullptr; + + base::move_handlers_from(src); } void reset() noexcept @@ -230,18 +735,37 @@ struct base_handlers : base_storage; + using base = base_access; }; // -template -struct base_handlers : base_storage +template +struct base_handlers + : base_access { + base_handlers() = default; + + template > + explicit base_handlers(Pmr* const mem_res) + : base{mem_res} + { + } + void copy_handlers_from(const base_handlers& src) noexcept { + value_mover_ = src.value_mover_; + value_copier_ = src.value_copier_; + base::copy_handlers_from(src); + } - value_copier_ = src.value_copier_; - value_mover_ = src.value_mover_; + void move_handlers_from(base_handlers& src) noexcept + { + value_mover_ = src.value_mover_; + value_copier_ = src.value_copier_; + src.value_mover_ = nullptr; + src.value_copier_ = nullptr; + + base::move_handlers_from(src); } void reset() noexcept @@ -259,34 +783,53 @@ struct base_handlers : base_storage; + using base = base_access; }; // base_handlers -// Copy policy. +// MARK: - Copy policy. // -template +template struct base_copy; // // Non-copyable case. -template -struct base_copy : base_handlers +template +struct base_copy + : base_handlers { - constexpr base_copy() = default; + base_copy() = default; base_copy(const base_copy&) = delete; base_copy& operator=(const base_copy&) = delete; + + template > + explicit base_copy(Pmr* const mem_res) + : base{mem_res} + { + } + +private: + using base = base_handlers; }; // // Copyable case. -template -struct base_copy : base_handlers +template +struct base_copy + : base_handlers { - constexpr base_copy() = default; + base_copy() = default; + base_copy(const base_copy& other) + : base{} { copy_from(other); } + template > + explicit base_copy(Pmr* const mem_res) + : base{mem_res} + { + } + base_copy& operator=(const base_copy& other) { base::reset(); @@ -310,54 +853,72 @@ struct base_copy : base_handlers; + using base = base_handlers; void copy_from(const base_copy& src) { CETL_DEBUG_ASSERT(!base::has_value(), "Expected to be empty before copying from source."); - if (src.has_value()) + if (base::copy_raw_storage_from(src)) { - base::copy_handlers_from(src); + CETL_DEBUG_ASSERT(nullptr != src.value_copier_, ""); - CETL_DEBUG_ASSERT(nullptr != base::value_copier_, ""); + src.value_copier_(src.get_raw_storage(), base::get_raw_storage()); - base::value_copier_(src.get_raw_storage(), base::get_raw_storage()); + base::copy_handlers_from(src); } } }; // base_copy -// Move policy. +// MARK: - Move policy. // -template +template struct base_move; // // Non-movable case. -template -struct base_move : base_copy +template +struct base_move + : base_copy { - constexpr base_move() = default; + base_move() = default; base_move(const base_move&) = default; base_move(base_move&&) noexcept = delete; base_move& operator=(const base_move&) = default; base_move& operator=(base_move&&) noexcept = delete; + + template > + explicit base_move(Pmr* const mem_res) + : base{mem_res} + { + } + +private: + using base = base_copy; }; // // Movable case. -template -struct base_move : base_copy +template +struct base_move + : base_copy { - constexpr base_move() = default; + base_move() = default; base_move(const base_move&) = default; base_move& operator=(const base_move&) = default; base_move(base_move&& other) noexcept + : base{} { move_from(other); } + template > + explicit base_move(Pmr* const mem_res) + : base{mem_res} + { + } + base_move& operator=(base_move&& other) noexcept { move_from(other); @@ -380,81 +941,154 @@ struct base_move : base_copy; + using base = base_copy; void move_from(base_move& src) noexcept { CETL_DEBUG_ASSERT(!base::has_value(), "Expected to be empty before moving from source."); - if (src.has_value()) + if (base::move_raw_storage_from(src)) { - base::copy_handlers_from(src); - - CETL_DEBUG_ASSERT(nullptr != base::value_mover_, ""); + CETL_DEBUG_ASSERT(nullptr != src.value_mover_, ""); - base::value_mover_(src.get_raw_storage(), base::get_raw_storage()); - - src.reset(); + src.value_mover_(src.get_raw_storage(), base::get_raw_storage()); } + + base::move_handlers_from(src); } }; // base_move -[[noreturn]] inline void throw_bad_unbounded_variant_access() -{ -#if defined(__cpp_exceptions) - throw bad_unbounded_variant_access(); -#else - std::terminate(); -#endif -} - } // namespace detail +template +std::add_pointer_t get_if(UnboundedVariant* operand) noexcept; + +template +std::add_pointer_t> get_if(const UnboundedVariant* operand) noexcept; + /// \brief The class `unbounded_variant` describes a type-safe container /// for single values of unbounded_variant copy and/or move constructible type. /// -/// \tparam Footprint Maximum size of a contained object (in bytes). +/// Size of a contained value must be less than or equal to `Footprint` to benefit small object optimization, +/// and it can't be bigger than `Footprint` in case of disabled Polymorphic Memory Resource (PMR) support. +/// +/// \tparam Footprint Maximum size of an in-place stored object (in bytes). /// \tparam Copyable Determines whether a contained object is copy constructible. /// \tparam Movable Determines whether a contained object is move constructible. /// \tparam Alignment Alignment of storage for a contained object. +/// \tparam Pmr Type of Polymorphic Memory Resource (PMR). Use `void` to disable PMR support. /// template -class unbounded_variant : detail::base_move + std::size_t Alignment = alignof(std::max_align_t), + typename Pmr = void> +class unbounded_variant : detail::base_move { - using base = detail::base_move; + using IsMovableT = std::integral_constant; + using base = detail::base_move; public: + using base::reset; + using base::has_value; + using base::valueless_by_exception; + /// \brief Constructs an empty `unbounded_variant` object. - constexpr unbounded_variant() = default; + /// + /// In case of enabled PMR support, the default memory resource is used. + /// + template > + unbounded_variant() + { + } + + /// \brief Constructs an empty `unbounded_variant` object with PMR support. + /// + /// \param mem_res Pointer to a memory resource to be used by the variant. + /// + template > + explicit unbounded_variant(Pmr* const mem_res) + : base{mem_res} + { + } + /// \brief Constructs an `unbounded_variant` object with a copy of the content of `other`. + /// + /// Any failure during the copy operation will result in the "valueless by exception" state. + /// In case of enabled PMR support, the memory resource pointer is copied from the `other` variant. + /// unbounded_variant(const unbounded_variant& other) = default; + /// \brief Constructs an `unbounded_variant` object with the content of `other` using move semantics. + /// + /// Any failure during the move operation will result in the "valueless by exception" state. + /// In case of enabled PMR support, the memory resource pointer is copied from the `other` variant. + /// unbounded_variant(unbounded_variant&& other) noexcept = default; - /// \brief Constructs an `unbounded_variant` object with `value` using move semantics. + /// \brief Constructs an `unbounded_variant` object by forwarding a value into variant's storage. /// - /// \tparam ValueType Type of the value to be stored. Its size must be less than or equal to `Footprint`. + /// Size of the value must be less than or equal to `Footprint` to benefit small object optimization, + /// and it can't be bigger than `Footprint` in case of disabled PMR support. + /// Any failure during the value forwarding will result in the "valueless by exception" state. + /// + /// In case of enabled PMR support, the default memory resource is used. + /// + /// \tparam ValueType Type of the value to be stored. + /// Its size must be less than or equal to `Footprint` in case of PMR support. + /// \param value Value to be stored. /// template , - typename = std::enable_if_t::value && - !pf17::detail::is_in_place_type::value>> + typename Tp = std::decay_t, + typename PmrAlias = Pmr, + typename = detail::EnableIfNotPmrT, + typename = std::enable_if_t::value && + !pf17::detail::is_in_place_type::value>> unbounded_variant(ValueType&& value) // NOLINT(*-explicit-constructor) { create(std::forward(value)); } + /// \brief Constructs an `unbounded_variant` object by forwarding a value into variant's storage. + /// + /// Size of the value must be less than or equal to `Footprint` to benefit small object optimization, + /// otherwise the value will be stored into PMR allocated storage. + /// Any failure during the value forwarding will result in the "valueless by exception" state. + /// + /// \tparam ValueType Type of the value to be stored. + /// \param mem_res Pointer to a memory resource to be used by the variant. + /// \param value Value to be stored. + /// + template , + typename PmrAlias = Pmr, + typename = detail::EnableIfPmrT, + typename = std::enable_if_t::value && + !pf17::detail::is_in_place_type::value>> + unbounded_variant(Pmr* const mem_res, ValueType&& value) + : base{mem_res} + { + create(std::forward(value)); + } + /// \brief Constructs an `unbounded_variant` object with in place constructed value. /// - /// \tparam ValueType Type of the value to be stored. Its size must be less than or equal to `Footprint`. + /// Size of the value must be less than or equal to `Footprint` to benefit small object optimization, + /// and it can't be bigger than `Footprint` in case of PMR support is disabled. + /// Any failure during the construction will result in the "valueless by exception" state. + /// + /// In case of enabled PMR support, the default memory resource is used. + /// + /// \tparam ValueType Type of the value to be stored. /// \tparam Args Types of arguments to be passed to the constructor of `ValueType`. /// \param args Arguments to be forwarded to the constructor of `ValueType`. /// - template > + template , + typename PmrAlias = Pmr, + typename = detail::EnableIfNotPmrT> explicit unbounded_variant(in_place_type_t, Args&&... args) { create(std::forward(args)...); @@ -462,108 +1096,235 @@ class unbounded_variant : detail::base_move, + typename PmrAlias = Pmr, + typename = detail::EnableIfPmrT> + explicit unbounded_variant(Pmr* const mem_res, in_place_type_t, Args&&... args) + : base{mem_res} + { + create(std::forward(args)...); + } + + /// \brief Constructs an `unbounded_variant` object with in place constructed value and initializer list. + /// + /// Size of the value must be less than or equal to `Footprint` to benefit small object optimization, + /// and it can't be bigger than `Footprint` in case of PMR support is disabled. + /// Any failure during the construction will result in the "valueless by exception" state. + /// + /// In case of enabled PMR support, the default memory resource is used. + /// /// \tparam ValueType Type of the value to be stored. Its size must be less than or equal to `Footprint`. /// \tparam Up Type of the elements of the initializer list. /// \tparam Args Types of arguments to be passed to the constructor of `ValueType`. /// \param list Initializer list to be forwarded to the constructor of `ValueType`. /// \param args Arguments to be forwarded to the constructor of `ValueType`. /// - template > + template , + typename PmrAlias = Pmr, + typename = detail::EnableIfNotPmrT> explicit unbounded_variant(in_place_type_t, std::initializer_list list, Args&&... args) { create(list, std::forward(args)...); } + /// \brief Constructs an `unbounded_variant` object with in place constructed value and initializer list. + /// + /// Size of the value must be less than or equal to `Footprint` to benefit small object optimization, + /// otherwise the value will be stored into PMR allocated storage. + /// Any failure during the construction will result in the "valueless by exception" state. + /// + /// \tparam ValueType Type of the value to be stored. Its size must be less than or equal to `Footprint`. + /// \tparam Up Type of the elements of the initializer list. + /// \tparam Args Types of arguments to be passed to the constructor of `ValueType`. + /// \param mem_res Pointer to a memory resource to be used by the variant. + /// \param list Initializer list to be forwarded to the constructor of `ValueType`. + /// \param args Arguments to be forwarded to the constructor of `ValueType`. + /// + template , + typename PmrAlias = Pmr, + typename = detail::EnableIfPmrT> + explicit unbounded_variant(Pmr* const mem_res, + in_place_type_t, + std::initializer_list list, + Args&&... args) + : base{mem_res} + { + create(list, std::forward(args)...); + } + /// \brief Destroys the contained object if there is one. + /// ~unbounded_variant() { reset(); } - /// \brief Assigns the content of `rhs` to `*this`. + /// \brief Assigns the content of `rhs` to `*this` using copy semantics. + /// + /// Any failure during the copy operation will result in the "valueless by exception" state. + /// In case of enabled PMR support, the memory resource pointer is copied from the `rhs` variant. + /// unbounded_variant& operator=(const unbounded_variant& rhs) { if (this != &rhs) { - unbounded_variant(rhs).swap(*this); + reset(); + base::operator=(rhs); } return *this; } /// \brief Assigns the content of `rhs` to `*this` using move semantics. + /// + /// Any failure during the move operation will result in the "valueless by exception" state. + /// In case of enabled PMR support, the memory resource pointer is copied from the `rhs` variant. + /// unbounded_variant& operator=(unbounded_variant&& rhs) noexcept { if (this != &rhs) { - unbounded_variant(std::move(rhs)).swap(*this); + reset(); + base::operator=(std::move(rhs)); } return *this; } - /// \brief Assigns `value` to `*this` using move semantics. + /// \brief Assigns `value` to `*this` by forwarding its value. /// - /// \tparam ValueType Type of the value to be stored. Its size must be less than or equal to `Footprint`. + /// Size of the value must be less than or equal to `Footprint` to benefit small object optimization, + /// and it can't be bigger than `Footprint` in case of PMR support is disabled. + /// Any failure during the forwarding will result in the "valueless by exception" state. + /// + /// \tparam ValueType Type of the value to be stored. /// template , typename = std::enable_if_t::value>> unbounded_variant& operator=(ValueType&& value) { - unbounded_variant(std::forward(value)).swap(*this); + reset(); + create(std::forward(value)); return *this; } /// \brief Emplaces a new value to `*this`. /// - /// \tparam ValueType Type of the value to be stored. Its size must be less than or equal to `Footprint`. + /// Size of the value must be less than or equal to `Footprint` to benefit small object optimization, + /// and it can't be bigger than `Footprint` in case of PMR support is disabled. + /// Any failure during the forwarding will result in the "valueless by exception" state. + /// + /// \tparam ValueType Type of the value to be stored. /// \tparam Args Types of arguments to be passed to the constructor of `ValueType`. /// \param args Arguments to be forwarded to the constructor of `ValueType`. /// template > - Tp& emplace(Args&&... args) + Tp* emplace(Args&&... args) { reset(); - return create(std::forward(args)...); } - /// \brief Emplaces a new value to `*this`. + /// \brief Emplaces a new value to `*this` using initializer list. /// - /// \tparam ValueType Type of the value to be stored. Its size must be less than or equal to `Footprint`. + /// Size of the value must be less than or equal to `Footprint` to benefit small object optimization, + /// and it can't be bigger than `Footprint` in case of PMR support is disabled. + /// Any failure during the emplacement will result in the "valueless by exception" state. + /// + /// \tparam ValueType Type of the value to be stored. /// \tparam Up Type of the elements of the initializer list. /// \tparam Args Types of arguments to be passed to the constructor of `ValueType`. /// \param list Initializer list to be forwarded to the constructor of `ValueType`. /// \param args Arguments to be forwarded to the constructor of `ValueType`. /// template > - Tp& emplace(std::initializer_list list, Args&&... args) + Tp* emplace(std::initializer_list list, Args&&... args) { reset(); - return create(list, std::forward(args)...); } - /// \brief If not empty, destroys the contained object. + /// \brief Swaps the content of `*this` with the content of `rhs` + /// using either move (if available) or copy semantics. /// - void reset() noexcept + /// In case of enabled PMR support, memory resource pointers are swapped as well. + /// + /// Any failure during the swap could result in the "valueless by exception" state, + /// and depending on which stage of swapping the failure happened + /// it could affect (invalidate) either of `*this` or `rhs` variants. + /// Use `valueless_by_exception()` method to check if a variant is in such failure state, + /// and `reset` (or `reset(Pmr*)`) method (or assign a new value) to recover from it. + /// + void swap(unbounded_variant& rhs) noexcept(Movable) { - base::reset(); + if (this == &rhs) + { + return; + } + + swapVariants(detail::IsPmr{}, IsMovableT{}, rhs); } - /// \brief Swaps the content of `*this` with the content of `rhs` using copy semantics. + /// \brief Gets current memory resource in use by the variant. /// - /// In use for copyable-only `unbounded_variant` objects. + template > + CETL_NODISCARD Pmr* get_memory_resource() const noexcept + { + return base::get_memory_resource(); + } + + /// \brief Resets the variant to empty state and assigns a new memory resource. /// - template > - void swap(unbounded_variant& rhs) + /// Useful to recover from the "valueless by exception" state (see `swap` method). + /// + template > + void reset(Pmr* const mem_res) noexcept { - if (this == &rhs) + base::reset(); + base::set_memory_resource(mem_res); + } + +private: + template + friend std::add_pointer_t get_if(UnboundedVariant* operand) noexcept; + + template + friend std::add_pointer_t> get_if(const UnboundedVariant* operand) noexcept; + + template + Tp* create(Args&&... args) + { + void* const raw_storage = base::alloc_new_raw_storage(sizeof(Tp)); + if (nullptr == raw_storage) { - return; + return nullptr; } + auto* ptr = new (raw_storage) Tp(std::forward(args)...); + + base::template make_handlers(); + + return ptr; + } + + void swapVariants(std::false_type /*IsPmr*/, std::false_type /*Movable*/, unbounded_variant& rhs) + { if (has_value()) { if (rhs.has_value()) @@ -585,18 +1346,40 @@ class unbounded_variant : detail::base_move> - void swap(unbounded_variant& rhs) noexcept + void swapVariants(std::true_type /*IsPmr*/, std::false_type /*Movable*/, unbounded_variant& rhs) { - if (this == &rhs) + if (has_value()) { - return; + if (rhs.has_value()) + { + unbounded_variant tmp{rhs}; + static_cast(rhs) = *this; + static_cast(*this) = tmp; + } + else + { + auto* const tmp_mr = rhs.get_memory_resource(); + static_cast(rhs) = *this; + reset(); + base::set_memory_resource(tmp_mr); + } + } + else if (rhs.has_value()) + { + auto* const tmp_mr = get_memory_resource(); + static_cast(*this) = rhs; + rhs.reset(); + rhs.set_memory_resource(tmp_mr); } + else + { + auto* const tmp_mr = base::set_memory_resource(rhs.get_memory_resource()); + rhs.set_memory_resource(tmp_mr); + } + } + void swapVariants(std::false_type /*IsPmr*/, std::true_type /*Movable*/, unbounded_variant& rhs) noexcept + { if (has_value()) { if (rhs.has_value()) @@ -616,23 +1399,34 @@ class unbounded_variant : detail::base_move - friend std::add_pointer_t get_if(UnboundedVariant* operand) noexcept; - - template - friend std::add_pointer_t> get_if(const UnboundedVariant* operand) noexcept; - - template - Tp& create(Args&&... args) + void swapVariants(std::true_type /*IsPmr*/, std::true_type /*Movable*/, unbounded_variant& rhs) noexcept { - base::template make_handlers(); - return *new (base::get_raw_storage()) Tp(std::forward(args)...); + if (has_value()) + { + if (rhs.has_value()) + { + unbounded_variant tmp{std::move(rhs)}; + static_cast(rhs) = std::move(*this); + static_cast(*this) = std::move(tmp); + } + else + { + auto* const tmp_mr = rhs.get_memory_resource(); + static_cast(rhs) = std::move(*this); + base::set_memory_resource(tmp_mr); + } + } + else if (rhs.has_value()) + { + auto* const tmp_mr = get_memory_resource(); + static_cast(*this) = std::move(rhs); + rhs.set_memory_resource(tmp_mr); + } + else + { + auto* const tmp_mr = base::set_memory_resource(rhs.get_memory_resource()); + rhs.set_memory_resource(tmp_mr); + } } }; // class unbounded_variant