diff --git a/flux-config/flux/config/features.hpp b/flux-config/flux/config/features.hpp index e9db079..0c2ac18 100644 --- a/flux-config/flux/config/features.hpp +++ b/flux-config/flux/config/features.hpp @@ -6,6 +6,12 @@ # define FLUX_NO_SANITIZE(...) #endif +#if __has_attribute(__no_sanitize__) +# define FLUX_NO_CFI __attribute__((__no_sanitize__("cfi"))) +#else +# define FLUX_NO_CFI /* nothing */ +#endif + #if __has_cpp_attribute(__gnu__::__always_inline__) # define FLUX_ALWAYS_INLINE [[__gnu__::__always_inline__]] #elif __has_cpp_attribute(clang::always_inline) diff --git a/flux-foundation/flux/foundation/containers/detail/temp_value.hpp b/flux-foundation/flux/foundation/containers/detail/temp_value.hpp new file mode 100644 index 0000000..475463f --- /dev/null +++ b/flux-foundation/flux/foundation/containers/detail/temp_value.hpp @@ -0,0 +1,37 @@ +#pragma once + +namespace flux::fou::detail { + +// clang-format off +template +struct temp_value final { + using allocator_traits = allocator_traits; + + union { + T value; + }; + Allocator& allocator; + + template + FLUX_NO_CFI constexpr explicit temp_value(Allocator& alloc, Args&&... args) noexcept + : allocator(alloc) { + construct_at(addressof(value), ::std::forward(args)...); + } + + temp_value(temp_value const&) = delete; + temp_value& operator=(temp_value const&) = delete; + + constexpr ~temp_value() requires meta::trivially_destructible = default; + constexpr ~temp_value() { destroy_at(addressof(value)); } + + constexpr T& get() noexcept { + return value; + } + + constexpr T const& get() const noexcept { + return value; + } +}; +// clang-format on + +} // namespace flux::fou::detail \ No newline at end of file diff --git a/flux-foundation/flux/foundation/containers/vector.hpp b/flux-foundation/flux/foundation/containers/vector.hpp index 3317368..b21ded9 100644 --- a/flux-foundation/flux/foundation/containers/vector.hpp +++ b/flux-foundation/flux/foundation/containers/vector.hpp @@ -1,5 +1,7 @@ #pragma once +#include + // TODO: // * Implement wrap_iter for vector; // * Implement rebind for std_allocator_adapter, so it can be fully compatible; @@ -53,7 +55,7 @@ class [[nodiscard, clang::trivial_abi]] vector final { public: using value_type = T; - using allocator_type = default_allocator_type; + using allocator_type = Allocator; using size_type = typename allocator_traits::size_type; using difference_type = typename allocator_traits::difference_type; using pointer = T*; @@ -76,7 +78,9 @@ class [[nodiscard, clang::trivial_abi]] vector final { FLUX_NO_UNIQUE_ADDRESS pointer end_cap_ = {}; FLUX_NO_UNIQUE_ADDRESS allocator_type allocator_ = {}; - constexpr vector() noexcept {} + constexpr vector() noexcept { + // Do nothing. + } constexpr explicit vector(size_type count) noexcept { if (count > 0) { @@ -156,7 +160,7 @@ class [[nodiscard, clang::trivial_abi]] vector final { } else { // Just clear the vector if we have enough capacity. clear(); } - + // Re-assign the vector with new values. end_ = ranges::uninitialized_fill_n(end_, difference_type(count), value); } // constexpr void assign(::std::initializer_list list) noexcept { @@ -199,6 +203,29 @@ class [[nodiscard, clang::trivial_abi]] vector final { resize(count, value_type{}); } + template + constexpr iterator emplace(const_iterator position, Args&&... args) noexcept { + pointer whereptr = begin_ + (position - begin()); + if (end_ < end_cap_) { + if (whereptr == end_) { + construct_one_at_end(::std::forward(args)...); + } else { + move_elements(/* from: */ whereptr, /* by: */ 1); + construct_in_place(whereptr, ::std::forward(args)...); + } + + return iterator{whereptr}; + } + + return iterator{emplace_reallocate(whereptr, ::std::forward(args)...)}; + } + + template + constexpr reference emplace_back(Args&&... args) noexcept { + FLUX_ASSERT(size() < capacity(), "emplace_back(args...) called on a full static_vector"); + return construct_one_at_end(::std::forward(args)...); + } + // Capacity [[nodiscard]] static constexpr size_type max_size() noexcept { return static_cast(::std::numeric_limits::max()); @@ -323,13 +350,13 @@ class [[nodiscard, clang::trivial_abi]] vector final { constexpr void vreallocate(size_type capacity) noexcept { FLUX_ASSERT(capacity != 0, "vreallocate(capacity) called with zero capacity"); - // clang-format off auto old_begin = begin_; auto old_end = end_; - [[maybe_unused]] auto old_capacity = static_cast(end_cap_ - begin_); - [[maybe_unused]] auto old_size = static_cast(end_ - begin_); + [[maybe_unused]] auto const old_capacity = static_cast(end_cap_ - begin_); + [[maybe_unused]] auto const old_size = static_cast(end_ - begin_); + // clang-format off auto [new_begin, new_capacity] = allocate_at_least(allocator_, capacity); pointer new_end; if constexpr (meta::relocatable) { @@ -343,13 +370,53 @@ class [[nodiscard, clang::trivial_abi]] vector final { new_end = ranges::uninitialized_copy_no_overlap(old_begin, old_end, new_begin); destroy_range(old_begin, old_end); } - FLUX_ASSERT(new_begin + old_size == new_end, - "vreallocate(capacity) failed to move memory"); + // clang-format on + FLUX_ASSERT(new_begin + old_size == new_end, "vreallocate(capacity) failed to move memory"); allocator_traits::deallocate(allocator_, old_begin, old_capacity); begin_ = new_begin; end_ = new_end; end_cap_ = new_begin + new_capacity; + } + + template + constexpr pointer emplace_reallocate(pointer const position, Args&&... args) noexcept { + // Reallocate and insert by perfectly forwarding `args` at `position`. + FLUX_ASSERT(end_ == end_cap_, "emplace_reallocate(position, args) called on a full vector"); + auto old_begin = begin_; + auto old_end = end_; + auto const old_size = static_cast(end_ - begin_); + auto const position_offset = static_cast(position - begin_); + + // clang-format off + auto const new_size = old_size + 1; + auto [new_begin, new_capacity] = + allocate_at_least(allocator_, detail::grow_twice(new_size)); // clang-format on + construct_in_place(new_begin + position_offset, ::std::forward(args)...); + + if (position == end_) { // at back, provide strong guarantee + if constexpr (meta::relocatable) { + ranges::uninitialized_relocate_no_overlap(old_begin, old_end, new_begin); + } else if constexpr (meta::sufficiently_move_constructible) { + ranges::uninitialized_move(old_begin, old_end, new_begin); + destroy_range(old_begin, old_end); + } else { + ranges::uninitialized_copy_no_overlap(old_begin, old_end, new_begin); + destroy_range(old_begin, old_end); + } + } else { // provide basic guarantee + // clang-format off + ranges::uninitialized_relocate_no_overlap(old_begin, old_end, new_begin); + ranges::uninitialized_relocate_no_overlap(position , old_end, + new_begin + position_offset + 1); + // clang-format on + } + allocator_traits::deallocate(allocator_, old_begin, capacity()); + begin_ = new_begin; + end_ = new_begin + new_size; + end_cap_ = new_begin + new_capacity; + + return begin_ + position_offset; } constexpr void vdeallocate() noexcept { @@ -431,6 +498,27 @@ class [[nodiscard, clang::trivial_abi]] vector final { constexpr void copy_assign_alloc(vector& other, meta::false_type) noexcept { (void)other; } + + // clang-format off + template + constexpr reference construct_one_at_end(Args&&... args) noexcept { + FLUX_ASSERT(end_ != end_cap_, "construct_one_at_end(args) called on a full vector"); + construct_in_place(end_, ::std::forward(args)...); + ++end_; + return back(); + } + + constexpr iterator move_elements(pointer position, meta::integral auto n) noexcept { + auto const elements_to_move = ranges::distance(position, end_); + end_ += static_cast(n); + + auto first = position; + auto last = ranges::next(first, elements_to_move); + auto result = ranges::next(first, static_cast(n) + elements_to_move); + ranges::uninitialized_relocate_backward(first, last, result); + return first; + } + // clang-format on }; } // namespace flux::fou \ No newline at end of file