From 92b50d4846d18a11cc76e447b5848788dda399e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Ba=C4=87?= Date: Wed, 15 Jan 2025 19:39:06 +0100 Subject: [PATCH 1/8] tirvial-abi - concepts::is_trivially_relocatable --- include/small_vectors/concepts/concepts.h | 15 + .../detail/uninitialized_constexpr.h | 67 ++++- include/small_vectors/detail/vector_func.h | 42 ++- unit_tests/uninitialized_constexpr_ut.cc | 273 ++++++++++++++---- 4 files changed, 319 insertions(+), 78 deletions(-) diff --git a/include/small_vectors/concepts/concepts.h b/include/small_vectors/concepts/concepts.h index 70a284f..c05a5ca 100644 --- a/include/small_vectors/concepts/concepts.h +++ b/include/small_vectors/concepts/concepts.h @@ -75,6 +75,21 @@ template concept trivially_destructible_after_move = explicit_trivially_destructible_after_move || std::is_trivially_destructible_v; +template +concept is_trivially_relocatable = + // !std::is_volatile_v> // && (relocatable_tag>::value(0) || + (std::is_trivially_move_constructible_v> + && std::is_trivially_move_assignable_v> + && std::is_trivially_destructible_v>) +#if __has_builtin(__is_trivially_relocatable) + || __is_trivially_relocatable(std::remove_all_extents_t) +#endif + ; +template +concept nothrow_relocatable_or_move_constr_and_constr_v + = (is_trivially_relocatable or std::is_nothrow_move_constructible_v) + and std::is_nothrow_constructible_v; + template concept same_as_any_of = std::disjunction_v...>; diff --git a/include/small_vectors/detail/uninitialized_constexpr.h b/include/small_vectors/detail/uninitialized_constexpr.h index dedba5b..285b1bc 100644 --- a/include/small_vectors/detail/uninitialized_constexpr.h +++ b/include/small_vectors/detail/uninitialized_constexpr.h @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace small_vectors::inline v3_3::detail @@ -64,20 +65,20 @@ template struct range_unwinder { using value_type = typename std::iterator_traits::value_type; - + static constexpr bool skip_unwind = use_nothrow or std::is_trivially_destructible_v; InputIt first_res_, last_; constexpr range_unwinder(InputIt first_res) noexcept : first_res_{first_res}, last_{first_res} {} - constexpr void release() + constexpr void release() noexcept { - if constexpr(use_nothrow || !std::is_trivially_destructible_v) + if constexpr(not skip_unwind) first_res_ = last_; } - constexpr ~range_unwinder() + constexpr ~range_unwinder() noexcept { - if constexpr(use_nothrow || !std::is_trivially_destructible_v) + if constexpr(not skip_unwind) if(first_res_ != last_) std::destroy(first_res_, last_); } @@ -177,6 +178,32 @@ template concept contiguous_iterator_with_trivialy_copy_constructible = std::contiguous_iterator && std::is_trivially_copy_constructible_v>; +template +inline auto uninitialized_trivial_memcpy(InputIterator first, size_type count, ForwardIterator result) + { + static constexpr auto elem_size{sizeof(std::iter_value_t)}; + if constexpr(std::contiguous_iterator and std::contiguous_iterator) + { + if(count > 0) + { + assert(std::addressof(*first) != nullptr); + assert(std::addressof(*result) != nullptr); + std::memcpy( + static_cast(std::addressof(*result)), + static_cast(std::addressof(*first)), + elem_size * std::size_t(count) + ); + } + } + else + { + small_vectors_clang_unsafe_buffer_usage_begin // + for(; count > 0; --count, (void)++first, ++result) + std::memcpy(std::addressof(*result), std::addressof(*first), elem_size); + small_vectors_clang_unsafe_buffer_usage_end // + } + } + // ------------------------------- // -- uninitialized_copy -- @@ -315,7 +342,7 @@ auto uninitialized_move_n_impl(InputIterator first, Size count, ForwardIterator contiguous_iterator_with_trivialy_move_constructible && contiguous_iterator_with_trivialy_move_constructible )); - constexpr bool use_nothrow = std::is_nothrow_constructible_v>; + constexpr bool use_nothrow = std::is_nothrow_move_constructible_v>; using unwind = range_unwinder; unwind cur{result}; small_vectors_clang_unsafe_buffer_usage_begin // @@ -391,13 +418,30 @@ inline constexpr void uninitialized_move_if_noexcept_n( // -- uninitialized_relocate -- template - requires(true == std::is_nothrow_move_constructible_v>) + requires( + concepts::is_trivially_relocatable> + or std::is_nothrow_move_constructible_v> + ) inline constexpr void uninitialized_relocate_n(InputIterator first, size_type count, ForwardIterator result) noexcept { using value_type = iterator_value_type_t; - uninitialized_move_n(first, count, result); - if constexpr(!concepts::trivially_destructible_after_move) - destroy_range(first, size_type(0u), count); + if(std::is_constant_evaluated()) + { + uninitialized_move_n(first, count, result); + if constexpr(not concepts::trivially_destructible_after_move) + destroy_range(first, size_type(0u), count); + } + else + { + if constexpr(concepts::is_trivially_relocatable) + uninitialized_trivial_memcpy(first, count, result); + else + { + uninitialized_move_n(first, count, result); + if constexpr(not concepts::trivially_destructible_after_move) + destroy_range(first, size_type(0u), count); + } + } } template @@ -407,7 +451,8 @@ inline constexpr void uninitialized_relocate_if_noexcept_n( { using value_type = iterator_value_type_t; - constexpr bool use_nothrow = std::is_nothrow_move_constructible_v>; + constexpr bool use_nothrow + = concepts::is_trivially_relocatable or std::is_nothrow_move_constructible_v; if constexpr(use_nothrow) uninitialized_relocate_n(first, count, result); else diff --git a/include/small_vectors/detail/vector_func.h b/include/small_vectors/detail/vector_func.h index 45f6e3e..9791cf4 100644 --- a/include/small_vectors/detail/vector_func.h +++ b/include/small_vectors/detail/vector_func.h @@ -465,16 +465,25 @@ inline constexpr void emplace_back_unchecked( ///\brief Appends a new element to the end of the container /// meets strong exception guarantee template - requires requires(vector_type & vec) { - requires vector_with_size_and_value; + requires + // clang-format off + requires(vector_type & vec) + { + requires vector_with_size_and_value; { vector_type::support_reallocation() } -> std::same_as; requires std::constructible_from; - } -inline constexpr vector_outcome_e emplace_back( - vector_type & vec, Args &&... args -) noexcept(concepts::is_nothrow_move_constr_and_constr_v) + } +// clang-format on +inline constexpr auto emplace_back(vector_type & vec, Args &&... args) + // clang-format off + noexcept + ( + std::is_nothrow_constructible_v + and ( concepts::is_trivially_relocatable + or std::is_nothrow_move_constructible_v ) + ) -> vector_outcome_e + // clang-format on { - constexpr bool use_nothrow = concepts::is_nothrow_move_constr_and_constr_v; internal_data_context_t const my{vec}; if(my.size() < my.capacity()) @@ -491,12 +500,19 @@ inline constexpr vector_outcome_e emplace_back( if(new_capacity != 0u) { using value_type = typename vector_type::value_type; + + static constexpr bool nothrow_constr = std::is_nothrow_constructible_v; + static constexpr bool nothrow_relocate + = concepts::is_trivially_relocatable or std::is_nothrow_move_constructible_v; + // alocate new space with growth factor, reclaim space in case of throwing at !use_nothrow - typename noexcept_if::cond_except_holder new_space{sv_allocate(new_capacity)}; + typename noexcept_if::cond_except_holder new_space{ + sv_allocate(new_capacity) + }; if(new_space) { // relocate elements - if constexpr(use_nothrow) + if constexpr(nothrow_constr and nothrow_relocate) // remains only for purprose of better data access order { detail::uninitialized_relocate_n(my.data(), my.size(), new_space.data()); @@ -506,16 +522,16 @@ inline constexpr vector_outcome_e emplace_back( else { // construct new element, if the second part can be noexcept relocated then this doesn't need raii unwind - typename noexcept_if>::cond_destroy_at el{ + typename noexcept_if::cond_destroy_at el{ std::construct_at(unext(new_space.data(), my.size()), std::forward(args)...) }; - // if only constructor throw use relocate for elements - if constexpr(std::is_nothrow_move_constructible_v) + // if only constructor not throws use relocate for elements + if constexpr(nothrow_relocate) detail::uninitialized_relocate_n(my.data(), my.size(), new_space.data()); else detail::uninitialized_relocate_with_copy_n(my.data(), my.size(), new_space.data()); // dont destroy if no exception is thrown with uninitialized_relocate_with_copy_n - // for std::is_nothrow_move_constructible_v is no op + // for nothrow_relocate is no op el.release(); } // deallocate old space diff --git a/unit_tests/uninitialized_constexpr_ut.cc b/unit_tests/uninitialized_constexpr_ut.cc index fd99b28..b945033 100644 --- a/unit_tests/uninitialized_constexpr_ut.cc +++ b/unit_tests/uninitialized_constexpr_ut.cc @@ -10,7 +10,9 @@ #include #include -using traits_list_move +small_vectors_clang_unsafe_buffer_usage_begin // + + using traits_list_move = metatests::type_list; template @@ -38,65 +40,114 @@ constexpr auto destroy_vec(std::array & sz) using namespace boost::ext::ut; static int * instance_counter{}; +static int * copy_counter{}; +static int * move_counter{}; +static int * destroy_counter{}; + +struct [[clang::trivial_abi]] explicit_trivially_relocatable_t + { + int i_; + + explicit explicit_trivially_relocatable_t(int i = 0) noexcept : i_(i) { ++(*instance_counter); } + + explicit_trivially_relocatable_t(explicit_trivially_relocatable_t && rhs) noexcept : i_(rhs.i_) + { + ++(*move_counter); + i_ = rhs.i_; + } + + explicit_trivially_relocatable_t(explicit_trivially_relocatable_t const & rhs) noexcept : i_(rhs.i_) + { + ++(*copy_counter); + i_ = rhs.i_; + } + + explicit_trivially_relocatable_t & operator=(explicit_trivially_relocatable_t const &) noexcept = default; + + // S & operator=(S &&) noexcept; + + ~explicit_trivially_relocatable_t() { ++(*destroy_counter); } + }; + +inline bool + operator==(explicit_trivially_relocatable_t const & lh, explicit_trivially_relocatable_t const & rh) noexcept + { + return lh.i_ == rh.i_; + } -struct explicit_relocatable_t +struct explicit_trivially_destructible_after_move_t { int i_; - explicit explicit_relocatable_t(int i = 0) noexcept : i_(i) { ++(*instance_counter); } + explicit explicit_trivially_destructible_after_move_t(int i = 0) noexcept : i_(i) { ++(*instance_counter); } - explicit_relocatable_t(explicit_relocatable_t && rhs) noexcept : i_(rhs.i_) + explicit_trivially_destructible_after_move_t(explicit_trivially_destructible_after_move_t && rhs) noexcept : + i_(rhs.i_) { - ++(*instance_counter); + ++(*move_counter); i_ = rhs.i_; } - explicit_relocatable_t(explicit_relocatable_t const & rhs) noexcept : i_(rhs.i_) + explicit_trivially_destructible_after_move_t(explicit_trivially_destructible_after_move_t const & rhs) noexcept : + i_(rhs.i_) { - ++(*instance_counter); + ++(*copy_counter); i_ = rhs.i_; } - explicit_relocatable_t & operator=(explicit_relocatable_t const &) noexcept = default; + explicit_trivially_destructible_after_move_t & operator=(explicit_trivially_destructible_after_move_t const & + ) noexcept + = default; // S & operator=(S &&) noexcept; - ~explicit_relocatable_t() { --(*instance_counter); } + ~explicit_trivially_destructible_after_move_t() { ++(*destroy_counter); } }; -inline bool operator==(explicit_relocatable_t const & lh, explicit_relocatable_t const & rh) noexcept +inline bool operator==( + explicit_trivially_destructible_after_move_t const & lh, explicit_trivially_destructible_after_move_t const & rh +) noexcept { return lh.i_ == rh.i_; } -consteval bool adl_decl_trivially_destructible_after_move(explicit_relocatable_t const *) { return true; } +consteval bool adl_decl_trivially_destructible_after_move(explicit_trivially_destructible_after_move_t const *) + { + return true; + } -struct implicit_relocatable_t +struct implicit_trivially_destructible_after_move_t { int i_; - explicit implicit_relocatable_t(int i) noexcept : i_(i) { ++(*instance_counter); } + explicit implicit_trivially_destructible_after_move_t(int i) noexcept : i_(i) { ++(*instance_counter); } - implicit_relocatable_t(implicit_relocatable_t && rhs) noexcept : i_(rhs.i_) + implicit_trivially_destructible_after_move_t(implicit_trivially_destructible_after_move_t && rhs) noexcept : + i_(rhs.i_) { - ++(*instance_counter); + ++(*move_counter); i_ = rhs.i_; } - implicit_relocatable_t(implicit_relocatable_t const & rhs) noexcept : i_(rhs.i_) + implicit_trivially_destructible_after_move_t(implicit_trivially_destructible_after_move_t const & rhs) noexcept : + i_(rhs.i_) { - ++(*instance_counter); + ++(*copy_counter); i_ = rhs.i_; } - implicit_relocatable_t & operator=(implicit_relocatable_t const &) noexcept = default; + implicit_trivially_destructible_after_move_t & operator=(implicit_trivially_destructible_after_move_t const & + ) noexcept + = default; // S & operator=(S &&) noexcept; - ~implicit_relocatable_t() = default; + ~implicit_trivially_destructible_after_move_t() = default; }; -inline bool operator==(implicit_relocatable_t const & lh, implicit_relocatable_t const & rh) noexcept +inline bool operator==( + implicit_trivially_destructible_after_move_t const & lh, implicit_trivially_destructible_after_move_t const & rh +) noexcept { return lh.i_ == rh.i_; } @@ -109,21 +160,21 @@ struct non_relocatable_t non_relocatable_t(non_relocatable_t && rhs) noexcept : i_(rhs.i_) { - ++(*instance_counter); + ++(*move_counter); i_ = rhs.i_; } non_relocatable_t(non_relocatable_t const & rhs) noexcept : i_(rhs.i_) { - ++(*instance_counter); + ++(*copy_counter); i_ = rhs.i_; } non_relocatable_t & operator=(non_relocatable_t const &) noexcept = default; - // S & operator=(S &&) noexcept; + non_relocatable_t & operator=(non_relocatable_t &&) noexcept; - ~non_relocatable_t() { --(*instance_counter); } + ~non_relocatable_t() { ++(*destroy_counter); } }; inline bool operator==(non_relocatable_t const & lh, non_relocatable_t const & rh) noexcept { return lh.i_ == rh.i_; } @@ -136,13 +187,13 @@ struct copy_non_relocatable_t copy_non_relocatable_t(copy_non_relocatable_t const & rhs) : i_(rhs.i_) { - ++(*instance_counter); + ++(*copy_counter); i_ = rhs.i_; } copy_non_relocatable_t & operator=(copy_non_relocatable_t const &) = default; - ~copy_non_relocatable_t() { --(*instance_counter); } + ~copy_non_relocatable_t() { ++(*destroy_counter); } }; inline bool operator==(copy_non_relocatable_t const & lh, copy_non_relocatable_t const & rh) noexcept @@ -150,20 +201,96 @@ inline bool operator==(copy_non_relocatable_t const & lh, copy_non_relocatable_t return lh.i_ == rh.i_; } -static_assert(std::is_trivially_destructible_v); -static_assert(!std::is_trivially_destructible_v); +static_assert(std::is_trivially_destructible_v); static_assert(!std::is_trivially_destructible_v); static_assert(!std::is_trivially_destructible_v); -static_assert(small_vectors::concepts::trivially_destructible_after_move); -static_assert(small_vectors::concepts::trivially_destructible_after_move); +static_assert(small_vectors::concepts::trivially_destructible_after_move); static_assert(small_vectors::concepts::trivially_destructible_after_move); static_assert(!small_vectors::concepts::trivially_destructible_after_move); static_assert(!small_vectors::concepts::trivially_destructible_after_move); +static_assert(not std::is_trivially_destructible_v); +static_assert(small_vectors::concepts::trivially_destructible_after_move); +static_assert(not small_vectors::concepts::is_trivially_relocatable); + +static_assert(not std::is_trivially_destructible_v); +static_assert(not small_vectors::concepts::trivially_destructible_after_move); +static_assert( + __has_builtin(__is_trivially_relocatable) + == small_vectors::concepts::is_trivially_relocatable +); + +struct counters + { + int ctr; + int cctr; + int mctr; + int dstr; + }; + int main() { + counters c; + instance_counter = &c.ctr; + copy_counter = &c.cctr; + move_counter = &c.mctr; + destroy_counter = &c.dstr; + using namespace metatests; + "test_range_unwinder"_test = [&] + { + "unwinding"_test = [&] + { + using T = copy_non_relocatable_t; + c = {}; + std::allocator alloc; + std::array source{T{1}, T{2}, T{3}, T{4}}; + try + { + small_vectors::detail::range_unwinder unw{source.begin()}; + unw.last_ = source.end(); + throw 0; + } + catch(...) + { + } + expect(eq(c.dstr, 4)) << "failed destructor count"; + }; + "no unwinding"_test = [&] + { + using T = copy_non_relocatable_t; + c = {}; + std::allocator alloc; + std::array source{T{1}, T{2}, T{3}, T{4}}; + try + { + small_vectors::detail::range_unwinder unw{source.begin()}; + unw.last_ = source.end(); + throw 0; + } + catch(...) + { + } + expect(eq(c.dstr, 0)) << "failed destructor count"; + }; + }; + + auto constexpr_uninitialized_trivial_memcpy_n = [](value_type const *) -> metatests::test_result + { + { + auto const arr1{construct_vec()}; + auto arr2{arr1}; + std::array out; + small_vectors::detail::uninitialized_trivial_memcpy(begin(arr2), 10, begin(out)); + constexpr_test(std::ranges::equal(arr1, out)); + destroy_vec(out); + return {}; + } + }; + using traits_list_trivial_memcpy_n + = metatests::type_list; + std::ignore = run_constexpr_test(constexpr_uninitialized_trivial_memcpy_n); "test_uninitialized_move_n"_test = [] { @@ -182,54 +309,92 @@ int main() std::ignore = run_constexpr_test(constexpr_uninitialized_move_n); std::ignore = run_consteval_test(constexpr_uninitialized_move_n); }; - "test_uninitialized_relocate_n"_test = [] + + "test_uninitialized_relocate_n"_test = [&] { - int ctr; - instance_counter = &ctr; - "relocatable"_test = [&] + auto constexpr_relocate_n = [](value_type const *) -> metatests::test_result + { + { + auto const arr1{construct_vec()}; + auto arr2{arr1}; + std::array out; + small_vectors::detail::uninitialized_relocate_if_noexcept_n(begin(arr2), 10, begin(out)); + constexpr_test(std::ranges::equal(arr1, out)); + destroy_vec(out); + return {}; + } + }; + std::ignore = run_constexpr_test(constexpr_relocate_n); + std::ignore = run_consteval_test(constexpr_relocate_n); + + "trivially_destructible_after_move"_test = [&] { - using T = explicit_relocatable_t; - ctr = 0; + using T = explicit_trivially_destructible_after_move_t; + c = {}; std::allocator alloc; std::array source{T{1}, T{2}, T{3}, T{4}}; T * ptr{alloc.allocate(4)}; small_vectors::detail::uninitialized_relocate_if_noexcept_n(source.begin(), 4u, ptr); - expect(ctr == 8) << "expecting destructor was not called 8 times" << ctr; - small_vectors_clang_unsafe_buffer_usage_begin // - expect(std::ranges::equal(std::span{source}, std::span{ptr, 4})); - small_vectors_clang_unsafe_buffer_usage_end // - alloc.deallocate(ptr, 4); + expect(eq(c.ctr, 4)) << "failed construction count"; + expect(eq(c.cctr, 0)) << "failed copy ctor count"; + expect(eq(c.mctr, 4)) << "failed move ctor count"; + expect(eq(c.dstr, 0)) << "failed destructor count"; + expect(std::ranges::equal(std::span{source}, std::span{ptr, 4})); + alloc.deallocate(ptr, 4); + }; + "trivially_relocatable"_test = [&] + { + using T = explicit_trivially_relocatable_t; + c = {}; + std::allocator alloc; + std::array source{T{1}, T{2}, T{3}, T{4}}; + T * ptr{alloc.allocate(4)}; + small_vectors::detail::uninitialized_relocate_if_noexcept_n(source.begin(), 4u, ptr); + expect(eq(c.ctr, 4)) << "failed construction count"; + expect(eq(c.cctr, 0)) << "failed copy ctor count"; +#if __has_builtin(__is_trivially_relocatable) + expect(eq(c.mctr, 0)) << "failed move ctor count"; + expect(eq(c.dstr, 0)) << "failed destructor count"; +#else + expect(eq(c.mctr, 4)) << "failed move ctor count"; + expect(eq(c.dstr, 4)) << "failed destructor count"; +#endif + expect(std::ranges::equal(std::span{source}, std::span{ptr, 4})); + alloc.deallocate(ptr, 4); }; "movable"_test = [&] { using T = non_relocatable_t; - ctr = 0; + c = {}; std::allocator alloc; std::array source{T{1}, T{2}, T{3}, T{4}}; T * ptr{alloc.allocate(4)}; small_vectors::detail::uninitialized_relocate_if_noexcept_n(source.begin(), 4u, ptr); - // range in ptr is not destroyed - expect(ctr == 4) << "expecting destructor was called 4 times by uninitialized_relocate_n" << ctr; - small_vectors_clang_unsafe_buffer_usage_begin // - expect(std::ranges::equal(std::span{source}, std::span{ptr, 4})); - small_vectors_clang_unsafe_buffer_usage_end // - alloc.deallocate(ptr, 4); + expect(eq(c.ctr, 4)) << "failed construction count"; + expect(eq(c.cctr, 0)) << "failed copy ctor count"; + expect(eq(c.mctr, 4)) << "failed move ctor count"; + expect(eq(c.dstr, 4)) << "failed destructor count"; + expect(std::ranges::equal(std::span{source}, std::span{ptr, 4})); + alloc.deallocate(ptr, 4); }; "copyable"_test = [&] { using T = copy_non_relocatable_t; - ctr = 0; + c = {}; std::allocator alloc; std::array source{T{1}, T{2}, T{3}, T{4}}; T * ptr{alloc.allocate(4)}; small_vectors::detail::uninitialized_relocate_if_noexcept_n(source.begin(), 4u, ptr); // range in ptr is not destroyed - expect(ctr == 4) << "expecting destructor was called 4 times by uninitialized_relocate_if_noexcept_n" << ctr; - small_vectors_clang_unsafe_buffer_usage_begin // - expect(std::ranges::equal(std::span{source}, std::span{ptr, 4})); - small_vectors_clang_unsafe_buffer_usage_end // - alloc.deallocate(ptr, 4); + expect(eq(c.ctr, 4)) << "failed construction count"; + expect(eq(c.cctr, 4)) << "failed copy ctor count"; + expect(eq(c.mctr, 0)) << "failed move ctor count"; + expect(eq(c.dstr, 4)) << "failed destructor count"; + expect(std::ranges::equal(std::span{source}, std::span{ptr, 4})); + + alloc.deallocate(ptr, 4); }; }; } +small_vectors_clang_unsafe_buffer_usage_end // From 51cabf88d20bbb3fcaf6860a91ef8958d0075811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Ba=C4=87?= Date: Wed, 15 Jan 2025 20:40:02 +0100 Subject: [PATCH 2/8] wip --- include/small_vectors/version.h | 2 +- unit_tests/uninitialized_constexpr_ut.cc | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/include/small_vectors/version.h b/include/small_vectors/version.h index 23710e9..a8ef80c 100644 --- a/include/small_vectors/version.h +++ b/include/small_vectors/version.h @@ -4,7 +4,7 @@ #pragma once -#define SMALL_VECTORS_VERSION "3.3.2" +#define SMALL_VECTORS_VERSION "3.3.3" #ifdef __clang__ #define small_vectors_clang_do_pragma(x) _Pragma(#x) diff --git a/unit_tests/uninitialized_constexpr_ut.cc b/unit_tests/uninitialized_constexpr_ut.cc index b945033..702c883 100644 --- a/unit_tests/uninitialized_constexpr_ut.cc +++ b/unit_tests/uninitialized_constexpr_ut.cc @@ -261,7 +261,6 @@ int main() { using T = copy_non_relocatable_t; c = {}; - std::allocator alloc; std::array source{T{1}, T{2}, T{3}, T{4}}; try { From 74f50ce7f0bb81aeca70532469d5a3a002daf9e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Ba=C4=87?= Date: Wed, 15 Jan 2025 20:45:19 +0100 Subject: [PATCH 3/8] wip --- unit_tests/uninitialized_constexpr_ut.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/unit_tests/uninitialized_constexpr_ut.cc b/unit_tests/uninitialized_constexpr_ut.cc index 702c883..5965963 100644 --- a/unit_tests/uninitialized_constexpr_ut.cc +++ b/unit_tests/uninitialized_constexpr_ut.cc @@ -244,7 +244,6 @@ int main() { using T = copy_non_relocatable_t; c = {}; - std::allocator alloc; std::array source{T{1}, T{2}, T{3}, T{4}}; try { From 85f5d452b9193e5991c62f8983028ecf5d2f227a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Ba=C4=87?= Date: Thu, 16 Jan 2025 01:56:42 +0100 Subject: [PATCH 4/8] wip --- CMakePresets.json | 13 +++- .../detail/uninitialized_constexpr.h | 4 +- include/small_vectors/detail/vector_func.h | 78 ++++++++++++------- 3 files changed, 63 insertions(+), 32 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 9dc1214..bee5850 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -15,7 +15,7 @@ "name": "cfg-ninja", "hidden": true, "generator": "Ninja", - "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } + "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } }, { "name": "cfg-c++20", @@ -41,6 +41,17 @@ "SMALL_VECTORS_ENABLE_LLD_LINKER" : "ON" } }, + { + "name": "clang-debug", + "generator": "Ninja", + "inherits": [ "cfg-ninja", "cfg-c++26" ], + "cacheVariables": { "CMAKE_CXX_COMPILER" : "clang++", "CMAKE_BUILD_TYPE": "Debug" } + }, + { + "name": "clang-20-release", + "inherits": [ "cfg-common", "cfg-ninja", "cfg-c++26" ], + "cacheVariables": { "CMAKE_CXX_COMPILER" : "clang++-20" } + }, { "name": "clang-19-release", "inherits": [ "cfg-common", "cfg-ninja", "cfg-c++26" ], diff --git a/include/small_vectors/detail/uninitialized_constexpr.h b/include/small_vectors/detail/uninitialized_constexpr.h index 285b1bc..431e5d2 100644 --- a/include/small_vectors/detail/uninitialized_constexpr.h +++ b/include/small_vectors/detail/uninitialized_constexpr.h @@ -186,13 +186,15 @@ inline auto uninitialized_trivial_memcpy(InputIterator first, size_type count, F { if(count > 0) { - assert(std::addressof(*first) != nullptr); + small_vectors_clang_unsafe_buffer_usage_begin // + assert(std::addressof(*first) != nullptr); assert(std::addressof(*result) != nullptr); std::memcpy( static_cast(std::addressof(*result)), static_cast(std::addressof(*first)), elem_size * std::size_t(count) ); + small_vectors_clang_unsafe_buffer_usage_end // } } else diff --git a/include/small_vectors/detail/vector_func.h b/include/small_vectors/detail/vector_func.h index 9791cf4..f414724 100644 --- a/include/small_vectors/detail/vector_func.h +++ b/include/small_vectors/detail/vector_func.h @@ -853,22 +853,30 @@ inline constexpr vector_outcome_e emplace( //------------------------------------------------------------------------------------------------------------------- template -concept reserve_constraints = requires { +concept reserve_constraints = requires +// clang-format off + { requires vector_with_size_and_value; - requires(true == std::is_move_constructible_v); - { vector_type::support_reallocation() } -> std::same_as; - requires(true == vector_type::support_reallocation()); -}; + requires concepts::is_trivially_relocatable + or std::is_move_constructible_v; + { vector_type::support_reallocation() } -> std::same_as; + requires vector_type::support_reallocation(); + }; +// clang-format on //------------------------------------------------------------------------------------------------------------------- template requires reserve_constraints -constexpr vector_outcome_e relocate_elements_dyn( - vector_type & vec, internal_data_context_t const & my, typename vector_type::size_type new_capacity -) noexcept(std::is_nothrow_move_constructible_v) +constexpr auto + relocate_elements_dyn(vector_type & vec, internal_data_context_t const & my, typename vector_type::size_type new_capacity) noexcept( + concepts::is_trivially_relocatable + or std::is_nothrow_move_constructible_v + ) -> vector_outcome_e { - constexpr bool use_nothrow = std::is_nothrow_move_constructible_v; using value_type = typename vector_type::value_type; + constexpr bool use_nothrow + = concepts::is_trivially_relocatable or std::is_nothrow_move_constructible_v; + // allocate new space with growth factor, reclaim space in case of throwing at !use_nothrow typename noexcept_if::cond_except_holder new_space{sv_allocate(new_capacity)}; if(new_space) @@ -889,11 +897,14 @@ constexpr vector_outcome_e relocate_elements_dyn( //------------------------------------------------------------------------------------------------------------------- template requires reserve_constraints -constexpr vector_outcome_e relocate_elements_static( - vector_type & vec, internal_data_context_t const & my -) noexcept(std::is_nothrow_move_constructible_v) +constexpr auto relocate_elements_static(vector_type & vec, internal_data_context_t const & my) noexcept( + concepts::is_trivially_relocatable + or std::is_nothrow_move_constructible_v +) -> vector_outcome_e { - constexpr bool use_nothrow = std::is_nothrow_move_constructible_v; + using value_type = typename vector_type::value_type; + constexpr bool use_nothrow + = concepts::is_trivially_relocatable or std::is_nothrow_move_constructible_v; typename noexcept_if::cond_except_holder_revert old_storage{vec, my.size(), vec.switch_static_priv_()}; if constexpr(use_nothrow) detail::uninitialized_relocate_n(my.begin(), my.size(), vec.begin()); @@ -910,9 +921,10 @@ constexpr vector_outcome_e relocate_elements_static( /// nothing. template requires reserve_constraints -constexpr vector_outcome_e reserve( - vector_type & vec, typename vector_type::size_type new_capacity -) noexcept(std::is_nothrow_move_constructible_v) +constexpr vector_outcome_e reserve(vector_type & vec, typename vector_type::size_type new_capacity) noexcept( + concepts::is_trivially_relocatable + or std::is_nothrow_move_constructible_v +) { if(new_capacity < max_size(vec)) { @@ -929,23 +941,25 @@ constexpr vector_outcome_e reserve( template concept vector_with_move_and_default_constructible_value_type = requires { requires vector_with_size_and_value; - requires(true == std::is_move_constructible_v); - requires(true == std::is_default_constructible_v); + requires concepts::is_trivially_relocatable + or std::is_move_constructible_v; + requires std::is_default_constructible_v; { vector_type::support_reallocation() } -> std::same_as; }; template -inline constexpr vector_outcome_e +inline constexpr auto default_append(vector_type & vec, internal_data_context_t const & my, typename vector_type::size_type count) noexcept( std::is_nothrow_move_constructible_v - && std::is_nothrow_default_constructible_v - ) + and std::is_nothrow_default_constructible_v + ) -> vector_outcome_e { using value_type = typename vector_type::value_type; using size_type = typename vector_type::size_type; constexpr bool use_nothrow - = std::is_nothrow_move_constructible_v && std::is_nothrow_default_constructible_v; + = (concepts::is_trivially_relocatable or std::is_nothrow_move_constructible_v) + and std::is_nothrow_default_constructible_v; if(count <= my.free_space()) { @@ -993,10 +1007,11 @@ inline constexpr vector_outcome_e } template -inline constexpr vector_outcome_e default_append(vector_type & vec, typename vector_type::size_type count) noexcept( - std::is_nothrow_move_constructible_v - && std::is_nothrow_default_constructible_v -) +inline constexpr auto default_append(vector_type & vec, typename vector_type::size_type count) noexcept( + (concepts::is_trivially_relocatable + or std::is_nothrow_move_constructible_v) + and std::is_nothrow_default_constructible_v +) -> vector_outcome_e { internal_data_context_t my{vec}; return default_append(vec, my, count); @@ -1005,8 +1020,9 @@ inline constexpr vector_outcome_e default_append(vector_type & vec, typename vec //------------------------------------------------------------------------------------------------------------------- template constexpr vector_outcome_e resize(vector_type & vec, typename vector_type::size_type new_size) noexcept( - std::is_nothrow_move_constructible_v - && std::is_nothrow_default_constructible_v + (concepts::is_trivially_relocatable + or std::is_nothrow_move_constructible_v) + and std::is_nothrow_default_constructible_v ) { internal_data_context_t const my{vec}; @@ -1033,8 +1049,10 @@ constexpr vector_outcome_e resize(vector_type & vec, typename vector_type::size_ //------------------------------------------------------------------------------------------------------------------- template requires reserve_constraints -constexpr vector_outcome_e - shrink_to_fit(vector_type & vec) noexcept(std::is_nothrow_move_constructible_v) +constexpr vector_outcome_e shrink_to_fit(vector_type & vec) noexcept( + concepts::is_trivially_relocatable + or std::is_nothrow_move_constructible_v +) { internal_data_context_t const my{vec}; if constexpr(vector_type::buffered_capacity() != 0) From 4c07b3d1ff042b9b5f6343764b5080a3b9fc10fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Ba=C4=87?= Date: Thu, 16 Jan 2025 02:21:24 +0100 Subject: [PATCH 5/8] wip --- .../detail/uninitialized_constexpr.h | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/include/small_vectors/detail/uninitialized_constexpr.h b/include/small_vectors/detail/uninitialized_constexpr.h index 431e5d2..65fe58b 100644 --- a/include/small_vectors/detail/uninitialized_constexpr.h +++ b/include/small_vectors/detail/uninitialized_constexpr.h @@ -180,6 +180,7 @@ concept contiguous_iterator_with_trivialy_copy_constructible template inline auto uninitialized_trivial_memcpy(InputIterator first, size_type count, ForwardIterator result) + -> ForwardIterator { static constexpr auto elem_size{sizeof(std::iter_value_t)}; if constexpr(std::contiguous_iterator and std::contiguous_iterator) @@ -191,11 +192,12 @@ inline auto uninitialized_trivial_memcpy(InputIterator first, size_type count, F assert(std::addressof(*result) != nullptr); std::memcpy( static_cast(std::addressof(*result)), - static_cast(std::addressof(*first)), + static_cast(std::addressof(*first)), elem_size * std::size_t(count) ); small_vectors_clang_unsafe_buffer_usage_end // } + return std::next(result, ptrdiff_t(count)); } else { @@ -203,6 +205,7 @@ inline auto uninitialized_trivial_memcpy(InputIterator first, size_type count, F for(; count > 0; --count, (void)++first, ++result) std::memcpy(std::addressof(*result), std::addressof(*first), elem_size); small_vectors_clang_unsafe_buffer_usage_end // + return result; } } @@ -213,12 +216,22 @@ template< contiguous_iterator_with_trivialy_copy_constructible InputIterator, std::integral Size, contiguous_iterator_with_trivialy_copy_constructible OutputIterator> -inline auto uninitialized_copy_n_impl(InputIterator first, Size count, OutputIterator out) +inline auto uninitialized_copy_n_impl(InputIterator first, Size count, OutputIterator out) -> OutputIterator { - // static constexpr auto elem_size{sizeof(std::iter_value_t)}; - return std::uninitialized_copy_n(first, count, out); - // std::memcpy(std::addressof(*out), std::addressof(*first), elem_size * std::size_t(count)); - // return std::next(out, std::ptrdiff_t(count)); + static constexpr auto elem_size{sizeof(std::iter_value_t)}; + if(count > 0) + { + small_vectors_clang_unsafe_buffer_usage_begin // + assert(std::addressof(*first) != nullptr); + assert(std::addressof(*out) != nullptr); + std::memmove( + static_cast(std::addressof(*out)), + static_cast(std::addressof(*first)), + elem_size * std::size_t(count) + ); + small_vectors_clang_unsafe_buffer_usage_end // + } + return std::next(out, ptrdiff_t(count)); } template From a3e839bba22735242ce377f25eba19c1937d05cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Ba=C4=87?= Date: Thu, 16 Jan 2025 19:40:36 +0100 Subject: [PATCH 6/8] add trivial relocation to storage --- include/small_vectors/detail/vector_storage.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/include/small_vectors/detail/vector_storage.h b/include/small_vectors/detail/vector_storage.h index b1dc5a4..4597441 100644 --- a/include/small_vectors/detail/vector_storage.h +++ b/include/small_vectors/detail/vector_storage.h @@ -186,7 +186,8 @@ struct static_vector_storage construct_move(static_vector_storage && rh) noexcept(std::is_nothrow_move_constructible_v) requires std::move_constructible { - constexpr bool use_nothrow = std::is_nothrow_move_constructible_v; + constexpr bool use_nothrow + = concepts::is_trivially_relocatable or std::is_nothrow_move_constructible_v; if constexpr(use_nothrow) uninitialized_relocate_n(rh.data(), rh.size_, data()); else @@ -198,7 +199,8 @@ struct static_vector_storage inline constexpr void assign_move(static_vector_storage && rh) noexcept(std::is_nothrow_move_assignable_v) requires std::movable { - constexpr bool use_nothrow = std::is_nothrow_move_assignable_v; + constexpr bool use_nothrow + = concepts::is_trivially_relocatable or std::is_nothrow_move_assignable_v; if constexpr(not std::is_trivially_destructible_v) { @@ -652,7 +654,8 @@ struct small_vector_storage construct_move(small_vector_storage && rh) noexcept(std::is_nothrow_move_constructible_v) requires std::move_constructible { - constexpr bool use_nothrow = std::is_nothrow_move_constructible_v; + constexpr bool use_nothrow + = concepts::is_trivially_relocatable or std::is_nothrow_move_constructible_v; if(rh.active_ == buffered) if constexpr(use_nothrow) @@ -671,7 +674,8 @@ struct small_vector_storage constexpr void assign_move(small_vector_storage && rh) noexcept(std::is_nothrow_move_assignable_v) requires std::movable { - constexpr bool use_nothrow = std::is_nothrow_move_assignable_v; + constexpr bool use_nothrow + = concepts::is_trivially_relocatable or std::is_nothrow_move_assignable_v; if constexpr(not std::is_trivially_destructible_v) { From 677006f140a7fee6034db02154d4a7a0c59eed20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Ba=C4=87?= Date: Thu, 16 Jan 2025 19:57:14 +0100 Subject: [PATCH 7/8] nodiscard, clang::trivial_abi on expected void --- include/small_vectors/utils/expected.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/small_vectors/utils/expected.h b/include/small_vectors/utils/expected.h index fca5cd3..a8887bb 100644 --- a/include/small_vectors/utils/expected.h +++ b/include/small_vectors/utils/expected.h @@ -27,7 +27,7 @@ using std::unexpect; using std::unexpect_t; using std::unexpected; } // namespace cxx23 - + #else #include @@ -187,7 +187,7 @@ class [[clang::trivial_abi]] unexpected { } - constexpr auto error() const & noexcept -> error_type const & { return error_; } + constexpr auto error() const & noexcept -> error_type const & { return error_; } constexpr auto error() & noexcept -> error_type & { return error_; } @@ -575,8 +575,8 @@ class [[nodiscard, clang::trivial_abi]] expected } template - inline constexpr void assign_value(other_value_type && v) - noexcept(std::is_nothrow_constructible_v) + inline constexpr void + assign_value(other_value_type && v) noexcept(std::is_nothrow_constructible_v) { if(has_value_) value_ = std::forward(v); @@ -588,8 +588,8 @@ class [[nodiscard, clang::trivial_abi]] expected } template - inline constexpr void assign_unexpected(other_error_type && v) - noexcept(std::is_nothrow_constructible_v) + inline constexpr void + assign_unexpected(other_error_type && v) noexcept(std::is_nothrow_constructible_v) { if(has_value_) { @@ -1010,7 +1010,7 @@ class [[nodiscard, clang::trivial_abi]] expected template requires std::same_as -class expected +class [[nodiscard, clang::trivial_abi]] expected { public: using value_type = void; From af3de39a362d8f8ad368de3ef11f970d96a312af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Ba=C4=87?= Date: Thu, 16 Jan 2025 20:07:35 +0100 Subject: [PATCH 8/8] update version --- include/small_vectors/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/small_vectors/version.h b/include/small_vectors/version.h index a8ef80c..e9bfcf8 100644 --- a/include/small_vectors/version.h +++ b/include/small_vectors/version.h @@ -4,7 +4,7 @@ #pragma once -#define SMALL_VECTORS_VERSION "3.3.3" +#define SMALL_VECTORS_VERSION "3.3.4" #ifdef __clang__ #define small_vectors_clang_do_pragma(x) _Pragma(#x)