diff --git a/include/dice/template-library/polymorphic_allocator.hpp b/include/dice/template-library/polymorphic_allocator.hpp index 3e90cff..153dc67 100644 --- a/include/dice/template-library/polymorphic_allocator.hpp +++ b/include/dice/template-library/polymorphic_allocator.hpp @@ -82,6 +82,9 @@ namespace dice::template_library { * This is mainly useful for scenarios where you cannot have dynamic polymorphism but still want * to be able to have multiple allocators, i.e. projects using persistent memory. * + * @note for propagate_on_container_copy_assignment (and others) we follow what std::scoped_allocator_adaptor does + * for composition of these values + * * @tparam T the type of object this allocator allocates * @tparam Allocators a list of the different allocator templates */ @@ -128,7 +131,7 @@ namespace dice::template_library { } template - constexpr polymorphic_allocator(std::in_place_index_t inp, Args &&...args) noexcept(std::is_nothrow_constructible_v>) + constexpr polymorphic_allocator(std::in_place_index_t inp, Args &&...args) noexcept(std::is_nothrow_constructible_v, decltype(std::forward(args))...>) : alloc_{inp, std::forward(args)...} { } @@ -197,6 +200,583 @@ namespace dice::template_library { } }; + template typename Allocator> + struct polymorphic_allocator { + using value_type = typename std::allocator_traits>::value_type; + using pointer = typename std::allocator_traits>::pointer; + using const_pointer = typename std::allocator_traits>::const_pointer; + using void_pointer = typename std::allocator_traits>::void_pointer; + using const_void_pointer = typename std::allocator_traits>::const_void_pointer; + + using propagate_on_container_copy_assignment = typename std::allocator_traits>::propagate_on_container_copy_assignment; + using propagate_on_container_move_assignment = typename std::allocator_traits>::propagate_on_container_move_assignment; + using propagate_on_container_swap = typename std::allocator_traits>::propagate_on_container_swap; + using is_always_equal = typename std::allocator_traits>::is_always_equal; + + template + struct rebind { + using other = polymorphic_allocator; + }; + + private: + template typename ...UAllocators> + friend struct polymorphic_allocator; + + using inner_type = Allocator; + inner_type alloc_; + + public: + constexpr polymorphic_allocator() noexcept(std::is_nothrow_default_constructible_v>) = default; + + template + constexpr polymorphic_allocator(std::in_place_type_t>, Args &&...args) noexcept(std::is_nothrow_constructible_v, decltype(std::forward(args))...>) + : alloc_{std::forward(args)...} { + } + + template + constexpr polymorphic_allocator(std::in_place_index_t<0>, Args &&...args) noexcept(std::is_nothrow_constructible_v, decltype(std::forward(args))...>) + : alloc_{std::forward(args)...} { + } + + constexpr polymorphic_allocator(Allocator const &alloc) noexcept(std::is_nothrow_copy_constructible_v>) + : alloc_{alloc} { + } + + constexpr polymorphic_allocator(Allocator &&alloc) noexcept(std::is_nothrow_move_constructible_v>) + : alloc_{std::move(alloc)} { + } + + template + constexpr polymorphic_allocator(polymorphic_allocator const &other) noexcept(std::is_nothrow_constructible_v, Allocator const &>) + : alloc_{other} { + } + + constexpr polymorphic_allocator(polymorphic_allocator const &other) noexcept(std::is_nothrow_copy_constructible_v>) = default; + constexpr polymorphic_allocator &operator=(polymorphic_allocator const &other) noexcept(std::is_nothrow_copy_assignable_v>) = default; + + constexpr polymorphic_allocator(polymorphic_allocator &&other) noexcept(std::is_nothrow_move_constructible_v>) = default; + constexpr polymorphic_allocator &operator=(polymorphic_allocator &&other) noexcept(std::is_nothrow_move_assignable_v>) = default; + + [[nodiscard]] constexpr pointer allocate(size_t n) noexcept(noexcept(std::allocator_traits>::allocate(std::declval &>(), n))) { + return std::allocator_traits>::allocate(alloc_, n); + } + + constexpr void deallocate(pointer ptr, size_t n) noexcept(noexcept(std::allocator_traits>::deallocate(std::declval &>(), ptr, n))) { + return std::allocator_traits>::deallocate(alloc_, ptr, n); + } + + constexpr bool operator==(polymorphic_allocator const &other) const noexcept = default; + constexpr bool operator!=(polymorphic_allocator const &other) const noexcept = default; + + friend constexpr void swap(polymorphic_allocator &lhs, polymorphic_allocator &rhs) noexcept(noexcept(std::swap(lhs.alloc_, rhs.alloc_))) { + std::swap(lhs.alloc_, rhs.alloc_); + } + + constexpr polymorphic_allocator select_on_container_copy_construction() const { + return polymorphic_allocator{std::allocator_traits>::select_on_container_copy_construction(alloc_)}; + } + + /** + * Checks if *this currently holds an instance of UAllocator + */ + template + [[nodiscard]] constexpr bool holds_allocator() const noexcept { + return std::is_same_v>; + } + + /** + * Checks if *this currently holds an instance of UAllocator + */ + template typename UAllocator> + [[nodiscard]] constexpr bool holds_allocator() const noexcept { + return std::is_same_v, Allocator>; + } + }; + + namespace discriminant2_detail { + enum struct Discriminant : bool { + First, + Second, + }; + } // namespace discriminant2_detail + + template typename Allocator1, template typename Allocator2> + struct polymorphic_allocator { + static_assert(!std::is_same_v, Allocator2>, + "Allocator types must only occur exactly once in the parameter list"); + + private: + using alloc1_traits = std::allocator_traits>; + using alloc2_traits = std::allocator_traits>; + + public: + using value_type = typename detail_pmr::same_type::type; + + using pointer = typename detail_pmr::same_type::type; + + using const_pointer = typename detail_pmr::same_type::type; + + using void_pointer = typename detail_pmr::same_type::type; + + using const_void_pointer = typename detail_pmr::same_type::type; + + using propagate_on_container_copy_assignment = std::bool_constant<(alloc1_traits::propagate_on_container_copy_assignment::value + || alloc2_traits::propagate_on_container_copy_assignment::value)>; + + using propagate_on_container_move_assignment = std::bool_constant<(alloc1_traits::propagate_on_container_move_assignment::value + || alloc2_traits::propagate_on_container_move_assignment::value)>; + + using propagate_on_container_swap = std::bool_constant<(alloc1_traits::propagate_on_container_swap::value + || alloc2_traits::propagate_on_container_swap::value)>; + + using is_always_equal = std::false_type; + + template + struct rebind { + using other = polymorphic_allocator; + }; + + private: + template typename ...UAllocators> + friend struct polymorphic_allocator; + + using discriminant_type = discriminant2_detail::Discriminant; + + union { + Allocator1 alloc1_; + Allocator2 alloc2_; + }; + + discriminant_type discriminant_; + + public: + constexpr polymorphic_allocator() noexcept(std::is_nothrow_default_constructible_v>) : discriminant_{discriminant_type::First} { + new (&alloc1_) Allocator1{}; + } + + template + constexpr polymorphic_allocator(std::in_place_type_t>, Args &&...args) noexcept(std::is_nothrow_constructible_v, decltype(std::forward(args))...>) + : discriminant_{discriminant_type::First} { + new (&alloc1_) Allocator1{std::forward(args)...}; + } + + template + constexpr polymorphic_allocator(std::in_place_type_t>, Args &&...args) noexcept(std::is_nothrow_constructible_v, decltype(std::forward(args))...>) + : discriminant_{discriminant_type::Second} { + new (&alloc2_) Allocator2{std::forward(args)...}; + } + + template + constexpr polymorphic_allocator(std::in_place_index_t<0>, Args &&...args) noexcept(std::is_nothrow_constructible_v, decltype(std::forward(args))...>) + : discriminant_{discriminant_type::First} { + new (&alloc1_) Allocator1{std::forward(args)...}; + } + + template + constexpr polymorphic_allocator(std::in_place_index_t<1>, Args &&...args) noexcept(std::is_nothrow_constructible_v, decltype(std::forward(args))...>) + : discriminant_{discriminant_type::Second} { + new (&alloc2_) Allocator2{std::forward(args)...}; + } + + constexpr polymorphic_allocator(Allocator1 const &alloc) noexcept(std::is_nothrow_copy_constructible_v>) + : discriminant_{discriminant_type::First} { + new (&alloc1_) Allocator1{alloc}; + } + + constexpr polymorphic_allocator(Allocator1 &&alloc) noexcept(std::is_nothrow_move_constructible_v>) + : discriminant_{discriminant_type::First} { + new (&alloc1_) Allocator1{std::move(alloc)}; + } + + constexpr polymorphic_allocator(Allocator2 const &alloc) noexcept(std::is_nothrow_copy_constructible_v>) + : discriminant_{discriminant_type::First} { + new (&alloc2_) Allocator2{alloc}; + } + + constexpr polymorphic_allocator(Allocator2 &&alloc) noexcept(std::is_nothrow_move_constructible_v>) + : discriminant_{discriminant_type::Second} { + new (&alloc2_) Allocator2{std::move(alloc)}; + } + + template + constexpr polymorphic_allocator(polymorphic_allocator const &other) noexcept(std::is_nothrow_constructible_v, Allocator1 const &> + && std::is_nothrow_constructible_v, Allocator2 const &>) + : discriminant_{other.discriminant_} { + + switch (discriminant_) { + case discriminant_type::First: { + new (&alloc1_) Allocator1{other.alloc1_}; + break; + } + case discriminant_type::Second: { + new (&alloc2_) Allocator2{other.alloc2_}; + break; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + } + + constexpr polymorphic_allocator(polymorphic_allocator const &other) noexcept(std::is_nothrow_copy_constructible_v> && std::is_nothrow_copy_constructible_v>) + : discriminant_{other.discriminant_} { + + switch (discriminant_) { + case discriminant_type::First: { + new (&alloc1_) Allocator1{other.alloc1_}; + break; + } + case discriminant_type::Second: { + new (&alloc2_) Allocator2{other.alloc2_}; + break; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + } + + constexpr polymorphic_allocator &operator=(polymorphic_allocator const &other) noexcept(std::is_nothrow_copy_assignable_v> + && std::is_nothrow_destructible_v> + && std::is_nothrow_copy_constructible_v> + && std::is_nothrow_copy_assignable_v> + && std::is_nothrow_destructible_v> + && std::is_nothrow_copy_constructible_v>) { + if (this == &other) [[unlikely]] { + return *this; + } + + switch (discriminant_) { + case discriminant_type::First: { + switch (other.discriminant_) { + case discriminant_type::First: { + alloc1_ = other.alloc1_; + break; + } + case discriminant_type::Second: { + alloc1_.~Allocator1(); + new (&alloc2_) Allocator2{other.alloc2_}; + break; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + break; + } + case discriminant_type::Second: { + switch (other.discriminant_) { + case discriminant_type::First: { + alloc2_.~Allocator2(); + new (&alloc1_) Allocator1{other.alloc1_}; + break; + } + case discriminant_type::Second: { + alloc2_ = other.alloc2_; + break; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + break; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + + discriminant_ = other.discriminant_; + return *this; + } + + constexpr polymorphic_allocator(polymorphic_allocator &&other) noexcept(std::is_nothrow_move_constructible_v> + && std::is_nothrow_move_constructible_v>) + : discriminant_{other.discriminant_} { + + switch (discriminant_) { + case discriminant_type::First: { + new (&alloc1_) Allocator1{std::move(other.alloc1_)}; + break; + } + case discriminant_type::Second: { + new (&alloc2_) Allocator2{std::move(other.alloc2_)}; + break; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + } + + constexpr polymorphic_allocator &operator=(polymorphic_allocator &&other) noexcept(std::is_nothrow_move_assignable_v> + && std::is_nothrow_destructible_v> + && std::is_nothrow_move_constructible_v> + && std::is_nothrow_move_assignable_v> + && std::is_nothrow_destructible_v> + && std::is_nothrow_move_constructible_v>) { + assert(this != &other); + + switch (discriminant_) { + case discriminant_type::First: { + switch (other.discriminant_) { + case discriminant_type::First: { + alloc1_ = std::move(other.alloc1_); + break; + } + case discriminant_type::Second: { + alloc1_.~Allocator1(); + new (&alloc2_) Allocator2{std::move(other.alloc2_)}; + break; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + break; + } + case discriminant_type::Second: { + switch (other.discriminant_) { + case discriminant_type::First: { + alloc2_.~Allocator2(); + new (&alloc1_) Allocator1{std::move(other.alloc1_)}; + break; + } + case discriminant_type::Second: { + alloc2_ = std::move(other.alloc2_); + break; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + break; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + + discriminant_ = other.discriminant_; + return *this; + } + + constexpr ~polymorphic_allocator() noexcept(std::is_nothrow_destructible_v> && std::is_nothrow_destructible_v>) { + switch (discriminant_) { + case discriminant_type::First: { + alloc1_.~Allocator1(); + break; + } + case discriminant_type::Second: { + alloc2_.~Allocator2(); + break; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + } + + [[nodiscard]] constexpr pointer allocate(size_t n) noexcept(noexcept(std::allocator_traits>::allocate(std::declval &>(), n)) + && noexcept(std::allocator_traits>::allocate(std::declval &>(), n))) { + switch (discriminant_) { + case discriminant_type::First: { + return std::allocator_traits>::allocate(alloc1_, n); + } + case discriminant_type::Second: { + return std::allocator_traits>::allocate(alloc2_, n); + } + default: { + assert(false); + __builtin_unreachable(); + } + } + } + + constexpr void deallocate(pointer ptr, size_t n) noexcept(noexcept(std::allocator_traits>::deallocate(std::declval &>(), ptr, n)) + && noexcept(std::allocator_traits>::deallocate(std::declval &>(), ptr, n))) { + switch (discriminant_) { + case discriminant_type::First: { + return std::allocator_traits>::deallocate(alloc1_, ptr, n); + } + case discriminant_type::Second: { + return std::allocator_traits>::deallocate(alloc2_, ptr, n); + } + default: { + assert(false); + __builtin_unreachable(); + } + } + } + + constexpr bool operator==(polymorphic_allocator const &other) const noexcept { + if (discriminant_ != other.discriminant_) { + return false; + } + + switch (discriminant_) { + case discriminant_type::First: { + return alloc1_ == other.alloc1_; + } + case discriminant_type::Second: { + return alloc2_ == other.alloc2_; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + } + constexpr bool operator!=(polymorphic_allocator const &other) const noexcept { + if (discriminant_ != other.discriminant_) { + return true; + } + + switch (discriminant_) { + case discriminant_type::First: { + return alloc1_ != other.alloc1_; + } + case discriminant_type::Second: { + return alloc2_ != other.alloc2_; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + } + + friend constexpr void swap(polymorphic_allocator &lhs, polymorphic_allocator &rhs) noexcept(std::is_nothrow_swappable_v> + && std::is_nothrow_destructible_v> + && std::is_nothrow_move_constructible_v> + && std::is_nothrow_swappable_v> + && std::is_nothrow_destructible_v> + && std::is_nothrow_move_constructible_v>) { + + switch (lhs.discriminant_) { + case discriminant_type::First: { + switch (rhs.discriminant_) { + case discriminant_type::First: { + std::swap(lhs.alloc1_, rhs.alloc1_); + break; + } + case discriminant_type::Second: { + Allocator1 tmp{std::move(lhs.alloc1_)}; + + lhs.alloc1_.~Allocator1(); + new (&lhs.alloc2_) Allocator2{std::move(rhs.alloc2_)}; + rhs.alloc2_.~Allocator2(); + new (&rhs.alloc1_) Allocator1{std::move(tmp)}; + break; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + break; + } + case discriminant_type::Second: { + switch (rhs.discriminant_) { + case discriminant_type::First: { + Allocator2 tmp{std::move(lhs.alloc2_)}; + + lhs.alloc2_.~Allocator2(); + new (&lhs.alloc1_) Allocator1{std::move(rhs.alloc1_)}; + rhs.alloc1_.~Allocator1(); + new (&rhs.alloc2_) Allocator2{std::move(tmp)}; + break; + } + case discriminant_type::Second: { + std::swap(lhs.alloc2_, rhs.alloc2_); + break; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + + break; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + + std::swap(lhs.discriminant_, rhs.discriminant_); + } + + constexpr polymorphic_allocator select_on_container_copy_construction() const { + switch (discriminant_) { + case discriminant_type::First: { + return polymorphic_allocator{std::allocator_traits>::select_on_container_copy_construction(alloc1_)}; + } + case discriminant_type::Second: { + return polymorphic_allocator{std::allocator_traits>::select_on_container_copy_construction(alloc2_)}; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + } + + /** + * Checks if *this currently holds an instance of UAllocator + */ + template + [[nodiscard]] constexpr bool holds_allocator() const noexcept { + switch (discriminant_) { + case discriminant_type::First: { + return std::is_same_v>; + } + case discriminant_type::Second: { + return std::is_same_v>; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + } + + /** + * Checks if *this currently holds an instance of UAllocator + */ + template typename UAllocator> + [[nodiscard]] constexpr bool holds_allocator() const noexcept { + switch (discriminant_) { + case discriminant_type::First: { + return std::is_same_v, Allocator1>; + } + case discriminant_type::Second: { + return std::is_same_v, Allocator2>; + } + default: { + assert(false); + __builtin_unreachable(); + } + } + } + }; + #if __has_include() /** * @brief Basically std::allocator but returning boost::interprocess::offset_ptr diff --git a/tests/tests_polymorphic_allocator.cpp b/tests/tests_polymorphic_allocator.cpp index 3bc8f26..25ca10e 100644 --- a/tests/tests_polymorphic_allocator.cpp +++ b/tests/tests_polymorphic_allocator.cpp @@ -5,11 +5,73 @@ #include #include +#include + +template +struct mallocator { + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using propagate_on_container_move_assignment = std::true_type; + using is_always_equal = std::false_type; + + int x = std::random_device{}(); + + mallocator() = default; + mallocator(mallocator const &) = default; + mallocator(mallocator &&) = default; + mallocator &operator=(mallocator const &) = default; + mallocator &operator=(mallocator &&) = default; + + template + mallocator(mallocator const &other) : x{other.x} { + } -TEST_SUITE("polymorphic_allocator") { - template - using poly_alloc_t = dice::template_library::polymorphic_allocator; + T *allocate(size_type n) { + return static_cast(malloc(sizeof(T) * n)); + } + + void deallocate(T *ptr, size_type) { + free(ptr); + } + + bool operator==(mallocator const &other) const noexcept = default; + bool operator!=(mallocator const &other) const noexcept = default; +}; +template +struct mallocator2 { + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using propagate_on_container_move_assignment = std::true_type; + using is_always_equal = std::false_type; + + int x = std::random_device{}(); + + mallocator2() = default; + mallocator2(mallocator2 const &) = default; + mallocator2(mallocator2 &&) = default; + mallocator2 &operator=(mallocator2 const &) = default; + mallocator2 &operator=(mallocator2 &&) = default; + + template + mallocator2(mallocator2 const &other) : x{other.x} { + } + + T *allocate(size_type n) { + return static_cast(malloc(sizeof(T) * n)); + } + + void deallocate(T *ptr, size_type) { + free(ptr); + } + + bool operator==(mallocator2 const &other) const noexcept = default; + bool operator!=(mallocator2 const &other) const noexcept = default; +}; + +TEST_SUITE("polymorphic_allocator") { TEST_CASE("offset_ptr_stl_allocator") { using alloc_t = dice::template_library::offset_ptr_stl_allocator; @@ -20,54 +82,151 @@ TEST_SUITE("polymorphic_allocator") { std::allocator_traits::deallocate(alloc, ptr, 1); } - TEST_CASE("polymorphic alloc") { + template + using poly_alloc2_t = dice::template_library::polymorphic_allocator; + + template + using poly_alloc3_t = dice::template_library::polymorphic_allocator; + + + template typename Alloc> + void run_test() { + SUBCASE("copy ctor") { + Alloc a{}; + + int *x = a.allocate(1); + *x = 5; + + Alloc b{a}; + CHECK_EQ(a, b); + + int *y = b.allocate(1); + *y = 5; + CHECK_EQ(*x, *y); + b.deallocate(x, 1); + b.deallocate(y, 1); + } + + SUBCASE("move ctor") { + Alloc a{}; + + int *x = a.allocate(1); + *x = 5; + + Alloc b{std::move(a)}; + + int *y = b.allocate(1); + *y = 5; + CHECK_EQ(*x, *y); + b.deallocate(x, 1); + b.deallocate(y, 1); + } + + SUBCASE("copy assign") { + Alloc a{}; + Alloc b{std::in_place_index<1>}; + + CHECK_NE(a, b); + a = b; + CHECK_EQ(a, b); + + int *x = a.allocate(1); + b.deallocate(x, 1); + } + + SUBCASE("move assign") { + Alloc a{}; + Alloc b{std::in_place_index<1>}; + + CHECK_NE(a, b); + a = std::move(b); + b = a; + CHECK_EQ(a, b); + + int *x = a.allocate(1); + b.deallocate(x, 1); + } + + SUBCASE("swap") { + Alloc a{}; + Alloc b{}; + Alloc c{std::in_place_index<1>}; + + CHECK_EQ(a, b); + CHECK_NE(a, c); + CHECK_NE(b, c); + + swap(a, b); + CHECK_EQ(a, b); + CHECK_NE(a, c); + CHECK_NE(b, c); + + swap(a, c); + CHECK_EQ(c, b); + CHECK_NE(a, b); + CHECK_NE(a, c); + + swap(a, b); + CHECK_EQ(a, c); + CHECK_NE(a, b); + CHECK_NE(b, c); + } + SUBCASE("with default alloc") { - poly_alloc_t alloc{}; + Alloc alloc{}; CHECK(alloc.template holds_allocator()); - auto ptr = std::allocator_traits>::allocate(alloc, 1); + auto ptr = std::allocator_traits>::allocate(alloc, 1); *ptr = 123; CHECK(*ptr == 123); - std::allocator_traits>::deallocate(alloc, ptr, 1); + std::allocator_traits>::deallocate(alloc, ptr, 1); } SUBCASE("in place construction") { SUBCASE("in place index") { - poly_alloc_t alloc{std::in_place_index<1>, std::pmr::get_default_resource()}; + Alloc alloc{std::in_place_index<1>}; - auto ptr = std::allocator_traits>::allocate(alloc, 1); + auto ptr = std::allocator_traits>::allocate(alloc, 1); *ptr = 456; CHECK(*ptr == 456); - std::allocator_traits>::deallocate(alloc, ptr, 1); + std::allocator_traits>::deallocate(alloc, ptr, 1); } SUBCASE("in place type") { - poly_alloc_t alloc{std::in_place_type>, std::pmr::get_default_resource()}; + Alloc alloc{std::in_place_type>}; - auto ptr = std::allocator_traits>::allocate(alloc, 1); + auto ptr = std::allocator_traits>::allocate(alloc, 1); *ptr = 456; CHECK(*ptr == 456); - std::allocator_traits>::deallocate(alloc, ptr, 1); + std::allocator_traits>::deallocate(alloc, ptr, 1); } } SUBCASE("type rebind") { - poly_alloc_t alloc{}; - poly_alloc_t alloc2{alloc}; + Alloc alloc{}; + Alloc alloc2{alloc}; } SUBCASE("implicit conversion") { - poly_alloc_t alloc{std::allocator{}}; + Alloc alloc{std::allocator{}}; CHECK(alloc.template holds_allocator()); - poly_alloc_t alloc2{std::pmr::polymorphic_allocator{}}; - CHECK(alloc2.template holds_allocator()); + Alloc alloc2{mallocator{}}; + CHECK(alloc2.template holds_allocator()); } SUBCASE("select on container copy construction") { - poly_alloc_t alloc{}; + Alloc alloc{}; auto alloc2 = alloc.select_on_container_copy_construction(); + + CHECK_EQ(alloc, alloc2); } } + + + TEST_CASE("polymorphic alloc") { + run_test(); + run_test(); + } }