diff --git a/README.md b/README.md index ff2f8ed..77c4889 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,23 @@ Note this is not part of the standard Library and should not be relied on once Example Usage: `static_assert(beman::has_constexpr_support>)`. +### Freestanding + +`beman::freestanding::inplace_vector` implements a minimal freestanding version of the specification, +which marks all potentially throwing functions as `= deleted`. +This is useful for platforms without exception support, as it will generate a compile-time error +instead of a potential runtime error when trying to use a throwing function. + +``` C++ +beman::inplace_vector iv; +iv.resize(0); // OK +iv.resize(10); // will throw or abort + +beman::freestanding::inplace_vector fs_iv; +fs_iv.resize(0); // will generate a compile-time error +fs_iv.resize(10); // will generate a compile-time error +``` + ## How to Build ### Compiler support diff --git a/include/beman/inplace_vector/inplace_vector.hpp b/include/beman/inplace_vector/inplace_vector.hpp index da3c45d..500b3f8 100644 --- a/include/beman/inplace_vector/inplace_vector.hpp +++ b/include/beman/inplace_vector/inplace_vector.hpp @@ -10,15 +10,12 @@ #include // for rotate... #include #include -#include // for lots... -#include // for size_t -#include // for fixed-width integer types -#include // for assertion diagnostics -#include // for less and equal_to -#include // for reverse_iterator and iterator traits -#include // for numeric_limits -#include // for destroy -#include // for operator new +#include // for lots... +#include // for size_t +#include // for fixed-width integer types +#include // for reverse_iterator and iterator traits +#include // for numeric_limits +#include // for destroy #include #include // for aligned_storage and all meta-functions @@ -81,7 +78,8 @@ concept lessthan_comparable = requires(const T &a, const T &b) { } // namespace beman::details::inplace_vector // Types implementing the `inplace_vector`'s storage -namespace beman::details::inplace_vector::storage { +namespace beman::details::inplace_vector { +namespace storage { // Storage for zero elements. template struct zero_sized { @@ -201,23 +199,15 @@ using storage_for = std::conditional_t< !satify_constexpr, non_trivial, std::conditional_t, trivial>>; -} // namespace beman::details::inplace_vector::storage - -namespace beman { - -template -concept has_constexpr_support = - details::inplace_vector::satify_constexpr; +} // namespace storage -/// Dynamically-resizable fixed-N vector with inplace storage. template -struct inplace_vector - : private details::inplace_vector::storage::storage_for { -private: +struct inplace_vector_base : private storage::storage_for { +protected: static_assert(std::is_nothrow_destructible_v, "T must be nothrow destructible"); - using base_t = details::inplace_vector::storage::storage_for; + + using base_t = storage::storage_for; using base_t::storage_data; using base_t::storage_size; using base_t::unsafe_set_size; @@ -235,10 +225,8 @@ struct inplace_vector using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; - // [containers.sequences.inplace_vector.cons], construct/copy/destroy - constexpr inplace_vector() noexcept = default; - // iterators + constexpr iterator begin() noexcept { return storage_data(); } constexpr const_iterator begin() const noexcept { return storage_data(); } constexpr iterator end() noexcept { return begin() + size(); } @@ -265,20 +253,18 @@ struct inplace_vector return const_reverse_iterator(cbegin()); } + // [inplace.vector.capacity], size/capacity + [[nodiscard]] constexpr bool empty() const noexcept { return storage_size() == 0; }; constexpr size_type size() const noexcept { return storage_size(); } + constexpr void shrink_to_fit() {} static constexpr size_type max_size() noexcept { return N; } static constexpr size_type capacity() noexcept { return N; } - constexpr void reserve(size_type n) { - if (n > N) [[unlikely]] { - BEMAN_IV_THROW_OR_ABORT(std::bad_alloc()); - } - } - constexpr void shrink_to_fit() {} // element access + constexpr reference operator[](size_type n) { return details::inplace_vector::index(*this, n); } @@ -302,17 +288,7 @@ struct inplace_vector constexpr T *data() noexcept { return storage_data(); } constexpr const T *data() const noexcept { return storage_data(); } - constexpr friend bool operator==(const inplace_vector &x, - const inplace_vector &y) { - return x.size() == y.size() && std::ranges::equal(x, y); - } - constexpr friend void swap(inplace_vector &x, inplace_vector &y) noexcept( - N == 0 || (std::is_nothrow_swappable_v && - std::is_nothrow_move_constructible_v)) { - x.swap(y); - } - -private: // Utilities +protected: // Utilities constexpr void assert_iterator_in_range([[maybe_unused]] const_iterator it) noexcept { IV_EXPECT(begin() <= it && "iterator not in range"); @@ -340,9 +316,7 @@ struct inplace_vector } public: - // Implementation - - // [containers.sequences.inplace_vector.modifiers], modifiers + // [inplace.vector.modifiers], modifiers template constexpr T &unchecked_emplace_back(Args &&...args) @@ -351,7 +325,7 @@ struct inplace_vector IV_EXPECT(size() < capacity() && "inplace_vector out-of-memory"); std::construct_at(end(), std::forward(args)...); unsafe_set_size(size() + size_type(1)); - return back(); + return this->back(); } template constexpr T *try_emplace_back(Args &&...args) { @@ -360,28 +334,6 @@ struct inplace_vector return &unchecked_emplace_back(std::forward(args)...); } - template - constexpr T &emplace_back(Args &&...args) - requires(std::constructible_from) - { - if (!try_emplace_back(std::forward(args)...)) [[unlikely]] { - BEMAN_IV_THROW_OR_ABORT(std::bad_alloc()); - } - return back(); - } - constexpr T &push_back(const T &x) - requires(std::constructible_from) - { - emplace_back(x); - return back(); - } - constexpr T &push_back(T &&x) - requires(std::constructible_from) - { - emplace_back(std::forward(x)); - return back(); - } - constexpr T *try_push_back(const T &x) requires(std::constructible_from) { @@ -404,44 +356,235 @@ struct inplace_vector return unchecked_emplace_back(std::forward(x)); } + template R> + constexpr std::ranges::borrowed_iterator_t try_append_range(R &&rg) + requires(std::constructible_from>) + { + auto it = std::ranges::begin(rg); + const auto end = std::ranges::end(rg); + for (; size() != capacity() && it != end; ++it) { + unchecked_emplace_back(*it); + } + return it; + } + + constexpr iterator erase(const_iterator first, const_iterator last) + requires(std::movable) + { + assert_iterator_pair_in_range(first, last); + iterator f = begin() + (first - begin()); + if (first != last) { + unsafe_destroy(std::move(f + (last - first), end(), f), end()); + unsafe_set_size(size() - static_cast(last - first)); + } + return f; + } + + constexpr iterator erase(const_iterator position) + requires(std::movable) + { + return erase(position, position + 1); + } + + constexpr void clear() noexcept { + unsafe_destroy(begin(), end()); + unsafe_set_size(0); + } + + constexpr void pop_back() { + IV_EXPECT(size() > 0 && "pop_back from empty inplace_vector!"); + if (size() > 0) { // UB fail-safe + unsafe_destroy(end() - 1, end()); + unsafe_set_size(size() - 1); + } + } + + constexpr friend bool operator==(const inplace_vector_base &x, + const inplace_vector_base &y) { + return x.size() == y.size() && std::ranges::equal(x, y); + } + + constexpr void swap(inplace_vector_base &x) noexcept( + N == 0 || (std::is_nothrow_swappable_v && + std::is_nothrow_move_constructible_v)) + requires(std::movable) + { + auto tmp = std::move(x); + x = std::move(*this); + (*this) = std::move(tmp); + } + + constexpr friend void + swap(inplace_vector_base &x, inplace_vector_base &y) noexcept( + N == 0 || (std::is_nothrow_swappable_v && + std::is_nothrow_move_constructible_v)) { + x.swap(y); + } + + constexpr friend auto operator<=>(const inplace_vector_base &x, + const inplace_vector_base &y) + requires(beman::details::inplace_vector::lessthan_comparable) + { + if constexpr (std::three_way_comparable) { + return std::lexicographical_compare_three_way(x.begin(), x.end(), + y.begin(), y.end()); + } else { + const auto sz = std::min(x.size(), y.size()); + for (std::size_t i = 0; i < sz; ++i) { + if (x[i] < y[i]) + return std::strong_ordering::less; + if (y[i] < x[i]) + return std::strong_ordering::greater; + // [container.opt.reqmts] < must be total ordering relationship + } + + return x.size() <=> y.size(); + } + } + + // [containers.sequences.inplace_vector.cons], construct/copy/destroy + + constexpr inplace_vector_base() noexcept = default; + + constexpr inplace_vector_base(const inplace_vector_base &x) + requires(N == 0 || std::is_trivially_copy_constructible_v) + = default; + + constexpr inplace_vector_base(const inplace_vector_base &x) + requires(N != 0 && !std::is_trivially_copy_constructible_v && + std::copyable) + { + for (auto &&e : x) + unchecked_emplace_back(e); + } + + constexpr inplace_vector_base(inplace_vector_base &&x) + requires(N == 0 || std::is_trivially_move_constructible_v) + = default; + + constexpr inplace_vector_base(inplace_vector_base &&x) + requires(N != 0 && !std::is_trivially_move_constructible_v && + std::movable) + { + for (auto &&e : x) + unchecked_emplace_back(std::move(e)); + } + + constexpr inplace_vector_base &operator=(const inplace_vector_base &x) + requires(N == 0 || (std::is_trivially_destructible_v && + std::is_trivially_copy_constructible_v && + std::is_trivially_copy_assignable_v)) + = default; + + constexpr inplace_vector_base &operator=(const inplace_vector_base &x) + requires(N != 0 && + !(std::is_trivially_destructible_v && + std::is_trivially_copy_constructible_v && + std::is_trivially_copy_assignable_v) && + std::copyable) + { + clear(); + for (auto &&e : x) + unchecked_emplace_back(e); + return *this; + } + + constexpr inplace_vector_base &operator=(inplace_vector_base &&x) + requires(N == 0 || (std::is_trivially_destructible_v && + std::is_trivially_move_constructible_v && + std::is_trivially_move_assignable_v)) + = default; + + constexpr inplace_vector_base &operator=(inplace_vector_base &&x) + requires(N != 0 && + !(std::is_trivially_destructible_v && + std::is_trivially_move_constructible_v && + std::is_trivially_move_assignable_v) && + std::movable) + { + clear(); + for (auto &&e : x) + unchecked_emplace_back(std::move(e)); + return *this; + } +}; + +} // namespace beman::details::inplace_vector + +namespace beman { + +template +concept has_constexpr_support = + details::inplace_vector::satify_constexpr; + +/// Dynamically-resizable fixed-N vector with inplace storage. +template +struct inplace_vector + : public details::inplace_vector::inplace_vector_base { + using value_type = T; + using pointer = T *; + using const_pointer = const T *; + using reference = value_type &; + using const_reference = const value_type &; + using size_type = size_t; + using difference_type = std::ptrdiff_t; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + // [containers.sequences.inplace_vector.modifiers], modifiers + + template + constexpr T &emplace_back(Args &&...args) + requires(std::constructible_from) + { + if (!this->try_emplace_back(std::forward(args)...)) [[unlikely]] { + BEMAN_IV_THROW_OR_ABORT(std::bad_alloc()); + } + return this->back(); + } + constexpr T &push_back(const T &x) + requires(std::constructible_from) + { + emplace_back(x); + return this->back(); + } + constexpr T &push_back(T &&x) + requires(std::constructible_from) + { + emplace_back(std::forward(x)); + return this->back(); + } + template R> constexpr void append_range(R &&rg) requires(std::constructible_from>) { if constexpr (std::ranges::sized_range) { - if (size() + std::ranges::size(rg) > capacity()) [[unlikely]] { + if (this->size() + std::ranges::size(rg) > this->capacity()) + [[unlikely]] { BEMAN_IV_THROW_OR_ABORT(std::bad_alloc()); } } for (auto &&e : rg) { - if (size() == capacity()) [[unlikely]] { + if (this->size() == this->capacity()) [[unlikely]] { BEMAN_IV_THROW_OR_ABORT(std::bad_alloc()); } emplace_back(std::forward(e)); } } - template R> - constexpr std::ranges::borrowed_iterator_t try_append_range(R &&rg) - requires(std::constructible_from>) - { - auto it = std::ranges::begin(rg); - const auto end = std::ranges::end(rg); - for (; size() != capacity() && it != end; ++it) { - unchecked_emplace_back(*it); - } - return it; - } - template constexpr iterator emplace(const_iterator position, Args &&...args) requires(std::constructible_from && std::movable) { - assert_iterator_in_range(position); - auto b = end(); + this->assert_iterator_in_range(position); + auto b = this->end(); emplace_back(std::forward(args)...); - auto pos = begin() + (position - begin()); - std::rotate(pos, b, end()); + auto pos = this->begin() + (position - this->begin()); + std::rotate(pos, b, this->end()); return pos; } @@ -451,18 +594,18 @@ struct inplace_vector requires(std::constructible_from> && std::movable) { - assert_iterator_in_range(position); + this->assert_iterator_in_range(position); if constexpr (std::random_access_iterator) { - if (size() + static_cast(std::distance(first, last)) > - capacity()) [[unlikely]] { + if (this->size() + static_cast(std::distance(first, last)) > + this->capacity()) [[unlikely]] { BEMAN_IV_THROW_OR_ABORT(std::bad_alloc()); } } - auto b = end(); + auto b = this->end(); for (; first != last; ++first) emplace_back(std::move(*first)); - auto pos = begin() + (position - begin()); - std::rotate(pos, b, end()); + auto pos = this->begin() + (position - this->begin()); + std::rotate(pos, b, this->end()); return pos; } @@ -486,12 +629,12 @@ struct inplace_vector constexpr iterator insert(const_iterator position, size_type n, const T &x) requires(std::constructible_from && std::copyable) { - assert_iterator_in_range(position); - auto b = end(); + this->assert_iterator_in_range(position); + auto b = this->end(); for (size_type i = 0; i < n; ++i) emplace_back(x); - auto pos = begin() + (position - begin()); - std::rotate(pos, b, end()); + auto pos = this->begin() + (position - this->begin()); + std::rotate(pos, b, this->end()); return pos; } @@ -507,251 +650,288 @@ struct inplace_vector return emplace(position, std::move(x)); } - constexpr inplace_vector(std::initializer_list il) + constexpr inplace_vector &operator=(std::initializer_list il) requires(std::constructible_from< T, std::ranges::range_reference_t>> && std::movable) { - insert(begin(), il); - } - - constexpr inplace_vector(size_type n, const T &value) - requires(std::constructible_from && std::copyable) - { - insert(begin(), n, value); - } - - constexpr explicit inplace_vector(size_type n) - requires(std::constructible_from && std::default_initializable) - { - for (size_type i = 0; i < n; ++i) - emplace_back(T{}); + assign_range(il); + return *this; } - template // BUGBUG: why not std::ranges::input_iterator? - constexpr inplace_vector(InputIterator first, InputIterator last) + template + constexpr void assign(InputIterator first, InputIterator last) requires(std::constructible_from> && std::movable) { - insert(begin(), first, last); + this->clear(); + insert(this->begin(), first, last); } - template R> - constexpr inplace_vector(beman::from_range_t, R &&rg) + constexpr void assign_range(R &&rg) requires(std::constructible_from> && std::movable) { - insert_range(begin(), std::forward(rg)); + assign(std::begin(rg), std::end(rg)); } - - constexpr iterator erase(const_iterator first, const_iterator last) - requires(std::movable) + constexpr void assign(size_type n, const T &u) + requires(std::constructible_from && std::movable) { - assert_iterator_pair_in_range(first, last); - iterator f = begin() + (first - begin()); - if (first != last) { - unsafe_destroy(std::move(f + (last - first), end(), f), end()); - unsafe_set_size(size() - static_cast(last - first)); - } - return f; + this->clear(); + insert(this->begin(), n, u); } - - constexpr iterator erase(const_iterator position) - requires(std::movable) + constexpr void assign(std::initializer_list il) + requires(std::constructible_from< + T, std::ranges::range_reference_t>> && + std::movable) { - return erase(position, position + 1); + this->clear(); + insert_range(this->begin(), il); } - constexpr void clear() noexcept { - unsafe_destroy(begin(), end()); - unsafe_set_size(0); + // [inplace.vector.capacity], size/capacity + + constexpr void reserve(size_type n) { + if (n > N) [[unlikely]] { + BEMAN_IV_THROW_OR_ABORT(std::bad_alloc()); + } } constexpr void resize(size_type sz, const T &c) requires(std::constructible_from && std::copyable) { - if (sz == size()) + if (sz == this->size()) return; else if (sz > N) [[unlikely]] { BEMAN_IV_THROW_OR_ABORT(std::bad_alloc()); - } else if (sz > size()) - insert(end(), sz - size(), c); + } else if (sz > this->size()) + insert(this->end(), sz - this->size(), c); else { - unsafe_destroy(begin() + sz, end()); - unsafe_set_size(sz); + this->unsafe_destroy(this->begin() + sz, this->end()); + this->unsafe_set_size(sz); } } constexpr void resize(size_type sz) requires(std::constructible_from && std::default_initializable) { - if (sz == size()) + if (sz == this->size()) return; else if (sz > N) [[unlikely]] { BEMAN_IV_THROW_OR_ABORT(std::bad_alloc()); - } else if (sz > size()) { - while (size() != sz) + } else if (sz > this->size()) { + while (this->size() != sz) emplace_back(T{}); } else { - unsafe_destroy(begin() + sz, end()); - unsafe_set_size(sz); + this->unsafe_destroy(this->begin() + sz, this->end()); + this->unsafe_set_size(sz); } } + // element access + constexpr reference at(size_type pos) { - if (pos >= size()) [[unlikely]] { + if (pos >= this->size()) [[unlikely]] { BEMAN_IV_THROW_OR_ABORT(std::out_of_range("inplace_vector::at")); } return details::inplace_vector::index(*this, pos); } constexpr const_reference at(size_type pos) const { - if (pos >= size()) [[unlikely]] { + if (pos >= this->size()) [[unlikely]] { BEMAN_IV_THROW_OR_ABORT(std::out_of_range("inplace_vector::at")); } return details::inplace_vector::index(*this, pos); } - constexpr void pop_back() { - IV_EXPECT(size() > 0 && "pop_back from empty inplace_vector!"); - unsafe_destroy(end() - 1, end()); - unsafe_set_size(size() - 1); - } + // [containers.sequences.inplace_vector.cons], construct/copy/destroy - constexpr inplace_vector(const inplace_vector &x) - requires(N == 0 || std::is_trivially_copy_constructible_v) - = default; + constexpr inplace_vector() noexcept = default; - constexpr inplace_vector(const inplace_vector &x) - requires(N != 0 && !std::is_trivially_copy_constructible_v && - std::copyable) + constexpr inplace_vector(std::initializer_list il) + requires(std::constructible_from< + T, std::ranges::range_reference_t>> && + std::movable) { - for (auto &&e : x) - unchecked_emplace_back(e); + insert(this->begin(), il); } - constexpr inplace_vector(inplace_vector &&x) - requires(N == 0 || std::is_trivially_move_constructible_v) - = default; - - constexpr inplace_vector(inplace_vector &&x) - requires(N != 0 && !std::is_trivially_move_constructible_v && - std::movable) + constexpr inplace_vector(size_type n, const T &value) + requires(std::constructible_from && std::copyable) { - for (auto &&e : x) - unchecked_emplace_back(std::move(e)); + insert(this->begin(), n, value); } - constexpr inplace_vector &operator=(const inplace_vector &x) - requires(N == 0 || (std::is_trivially_destructible_v && - std::is_trivially_copy_constructible_v && - std::is_trivially_copy_assignable_v)) - = default; - - constexpr inplace_vector &operator=(const inplace_vector &x) - requires(N != 0 && - !(std::is_trivially_destructible_v && - std::is_trivially_copy_constructible_v && - std::is_trivially_copy_assignable_v) && - std::copyable) + constexpr explicit inplace_vector(size_type n) + requires(std::constructible_from && std::default_initializable) { - clear(); - for (auto &&e : x) - unchecked_emplace_back(e); - return *this; + for (size_type i = 0; i < n; ++i) + emplace_back(T{}); } - constexpr inplace_vector &operator=(inplace_vector &&x) - requires(N == 0 || (std::is_trivially_destructible_v && - std::is_trivially_move_constructible_v && - std::is_trivially_move_assignable_v)) - = default; + template // BUGBUG: why not std::ranges::input_iterator? + constexpr inplace_vector(InputIterator first, InputIterator last) + requires(std::constructible_from> && + std::movable) + { + insert(this->begin(), first, last); + } - constexpr inplace_vector &operator=(inplace_vector &&x) - requires(N != 0 && - !(std::is_trivially_destructible_v && - std::is_trivially_move_constructible_v && - std::is_trivially_move_assignable_v) && + template R> + constexpr inplace_vector(beman::from_range_t, R &&rg) + requires(std::constructible_from> && std::movable) { - clear(); - for (auto &&e : x) - unchecked_emplace_back(std::move(e)); - return *this; + insert_range(this->begin(), std::forward(rg)); } +}; - constexpr inplace_vector &operator=(std::initializer_list il) +namespace freestanding { +template +struct inplace_vector + : public details::inplace_vector::inplace_vector_base { + using value_type = T; + using pointer = T *; + using const_pointer = const T *; + using reference = value_type &; + using const_reference = const value_type &; + using size_type = size_t; + using difference_type = std::ptrdiff_t; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + // [containers.sequences.inplace_vector.modifiers], modifiers + + template + constexpr T &emplace_back(Args &&...args) + requires(std::constructible_from) + = delete; + constexpr T &push_back(const T &x) + requires(std::constructible_from) + = delete; + constexpr T &push_back(T &&x) + requires(std::constructible_from) + = delete; + + template R> + constexpr void append_range(R &&rg) + requires(std::constructible_from>) + = delete; + + template + constexpr iterator emplace(const_iterator position, Args &&...args) + requires(std::constructible_from && std::movable) + = delete; + + template + constexpr iterator insert(const_iterator position, InputIterator first, + InputIterator last) + requires(std::constructible_from> && + std::movable) + = delete; + + template R> + constexpr iterator insert_range(const_iterator position, R &&rg) + requires(std::constructible_from> && + std::movable) + = delete; + + constexpr iterator insert(const_iterator position, + std::initializer_list il) requires(std::constructible_from< T, std::ranges::range_reference_t>> && std::movable) - { - assign_range(il); - return *this; - } + = delete; - constexpr void - swap(inplace_vector &x) noexcept(N == 0 || - (std::is_nothrow_swappable_v && - std::is_nothrow_move_constructible_v)) - requires(std::movable) - { - auto tmp = std::move(x); - x = std::move(*this); - (*this) = std::move(tmp); - } + constexpr iterator insert(const_iterator position, size_type n, const T &x) + requires(std::constructible_from && std::copyable) + = delete; + + constexpr iterator insert(const_iterator position, const T &x) + requires(std::constructible_from && std::copyable) + = delete; + + constexpr iterator insert(const_iterator position, T &&x) + requires(std::constructible_from && std::movable) + = delete; + + constexpr inplace_vector &operator=(std::initializer_list il) + requires(std::constructible_from< + T, std::ranges::range_reference_t>> && + std::movable) + = delete; template constexpr void assign(InputIterator first, InputIterator last) requires(std::constructible_from> && std::movable) - { - clear(); - insert(begin(), first, last); - } + = delete; template R> constexpr void assign_range(R &&rg) requires(std::constructible_from> && std::movable) - { - assign(std::begin(rg), std::end(rg)); - } + = delete; constexpr void assign(size_type n, const T &u) requires(std::constructible_from && std::movable) - { - clear(); - insert(begin(), n, u); - } + = delete; constexpr void assign(std::initializer_list il) requires(std::constructible_from< T, std::ranges::range_reference_t>> && std::movable) - { - clear(); - insert_range(begin(), il); - } + = delete; - constexpr friend auto operator<=>(const inplace_vector &x, - const inplace_vector &y) - requires(beman::details::inplace_vector::lessthan_comparable) - { - if constexpr (std::three_way_comparable) { - return std::lexicographical_compare_three_way(x.begin(), x.end(), - y.begin(), y.end()); - } else { - const auto sz = std::min(x.size(), y.size()); - for (std::size_t i = 0; i < sz; ++i) { - if (x[i] < y[i]) - return std::strong_ordering::less; - if (y[i] < x[i]) - return std::strong_ordering::greater; - // [container.opt.reqmts] < must be total ordering relationship - } + // [inplace.vector.capacity], size/capacity - return x.size() <=> y.size(); - } - } + constexpr void reserve(size_type n) = delete; + + constexpr void resize(size_type sz, const T &c) + requires(std::constructible_from && std::copyable) + = delete; + constexpr void resize(size_type sz) + requires(std::constructible_from && std::default_initializable) + = delete; + + // element access + + constexpr reference at(size_type pos) = delete; + constexpr const_reference at(size_type pos) const = delete; + + // [containers.sequences.inplace_vector.cons], construct/copy/destroy + + constexpr inplace_vector() noexcept = default; + + constexpr inplace_vector(std::initializer_list il) + requires(std::constructible_from< + T, std::ranges::range_reference_t>> && + std::movable) + = delete; + + constexpr inplace_vector(size_type n, const T &value) + requires(std::constructible_from && std::copyable) + = delete; + + constexpr explicit inplace_vector(size_type n) + requires(std::constructible_from && std::default_initializable) + = delete; + + template // BUGBUG: why not std::ranges::input_iterator? + constexpr inplace_vector(InputIterator first, InputIterator last) + requires(std::constructible_from> && + std::movable) + = delete; + + template R> + constexpr inplace_vector(beman::from_range_t, R &&rg) + requires(std::constructible_from> && + std::movable) + = delete; }; +} // namespace freestanding + template -constexpr std::size_t erase(inplace_vector &c, const U &value) { +constexpr std::size_t +erase(details::inplace_vector::inplace_vector_base &c, const U &value) { auto it = std::remove(c.begin(), c.end(), value); auto r = std::distance(it, c.end()); c.erase(it, c.end()); @@ -759,7 +939,9 @@ constexpr std::size_t erase(inplace_vector &c, const U &value) { } template -constexpr std::size_t erase_if(inplace_vector &c, Predicate pred) { +constexpr std::size_t +erase_if(details::inplace_vector::inplace_vector_base &c, + Predicate pred) { auto it = std::remove_if(c.begin(), c.end(), pred); auto r = std::distance(it, c.end()); c.erase(it, c.end()); diff --git a/tests/beman/inplace_vector/CMakeLists.txt b/tests/beman/inplace_vector/CMakeLists.txt index 2f93633..fa9e2c3 100644 --- a/tests/beman/inplace_vector/CMakeLists.txt +++ b/tests/beman/inplace_vector/CMakeLists.txt @@ -42,6 +42,7 @@ add_gtest(constructors) add_gtest(size_n_data) add_gtest(erasure) add_gtest(modifiers) +add_gtest(freestanding) # only add noexception tests if NO_EXCEPTIONS option is set and compiler supports -fno-exceptions if( diff --git a/tests/beman/inplace_vector/constexpr.test.cpp b/tests/beman/inplace_vector/constexpr.test.cpp index 2d44642..965b4ec 100644 --- a/tests/beman/inplace_vector/constexpr.test.cpp +++ b/tests/beman/inplace_vector/constexpr.test.cpp @@ -1,5 +1,7 @@ -#include #include + +#include +#include #include // used for testing beman::has_constexpr_support diff --git a/tests/beman/inplace_vector/constructors.test.cpp b/tests/beman/inplace_vector/constructors.test.cpp index 556f678..98fc55c 100644 --- a/tests/beman/inplace_vector/constructors.test.cpp +++ b/tests/beman/inplace_vector/constructors.test.cpp @@ -134,4 +134,26 @@ TYPED_TEST(Constructors, CopyRanges) { } } +TYPED_TEST(Constructors, freestandingConversion) { + using T = TestFixture::T; + + using IV = beman::inplace_vector; + using FS = beman::freestanding::inplace_vector; + + static_assert(std::is_constructible_v); + static_assert(std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + + static_assert(std::is_assignable_v); + static_assert(std::is_assignable_v); + static_assert(!std::is_assignable_v); + static_assert(!std::is_assignable_v); + + static_assert(std::is_convertible_v); + static_assert(std::is_convertible_v); + static_assert(!std::is_convertible_v); + static_assert(!std::is_convertible_v); +} + }; // namespace diff --git a/tests/beman/inplace_vector/freestanding.test.cpp b/tests/beman/inplace_vector/freestanding.test.cpp new file mode 100644 index 0000000..9975ea2 --- /dev/null +++ b/tests/beman/inplace_vector/freestanding.test.cpp @@ -0,0 +1,289 @@ +#include +#include +#include + +// constexpr void assign(InputIterator first, InputIterator last); +template +concept has_assign_iterator = requires(T t, T::iterator it) { + { t.assign(it, it) } -> std::same_as; +}; + +// constexpr void assign_range(R&& rg); +template +concept has_assign_range = requires(T t, R rg) { + { t.assign_range(rg) } -> std::same_as; +}; + +// constexpr void assign(size_type n, const T& u); +template +concept has_assign_size = requires(T t, T::size_type n, T::const_reference u) { + { t.assign(n, u) } -> std::same_as; +}; + +// constexpr void assign(initializer_list il); +template +concept has_assign_initializer = + requires(T t, std::initializer_list il) { + { t.assign(il) } -> std::same_as; + }; + +// constexpr void resize(size_type sz); +template +concept has_resize = requires(T t, T::size_type sz) { + { t.resize(sz) } -> std::same_as; +}; + +// constexpr void resize(size_type sz, const T& c); +template +concept has_resize_ref = + requires(T t, typename T::size_type sz, T::const_reference c) { + { t.resize(sz, c) } -> std::same_as; + }; + +// void reserve(size_type n); +template +concept has_reserve = requires(T t, T::size_type n) { + { t.reserve(n) } -> std::same_as; +}; + +// constexpr reference at(size_type n); +template +concept has_at = requires(T t, T::size_type n) { + { t.at(n) } -> std::same_as; +}; + +// constexpr const_reference at(size_type n) const; +template +concept has_const_at = requires(const T t, T::size_type n) { + { t.at(n) } -> std::same_as; +}; + +// constexpr reference emplace_back(Args&&... args); +template +concept has_emplace_back = requires(T t, T::value_type v) { + { t.emplace_back(v) } -> std::same_as; +}; + +// constexpr reference push_back(const T& x); +template +concept has_push_back_const = requires(T t, typename T::const_reference x) { + { t.push_back(x) } -> std::same_as; +}; + +// constexpr reference push_back(T&& x); +template +concept has_push_back_rv = requires(T t, typename T::value_type x) { + { + t.push_back(static_cast(x)) + } -> std::same_as; +}; + +// constexpr void append_range(R&& rg); +template +concept has_append_range = requires(T t, R &&r) { + { t.append_range(r) } -> std::same_as; +}; + +// constexpr iterator emplace(const_iterator position, Args&&... args); +template +concept has_emplace = requires(T t, T::const_iterator it, T::value_type v) { + { t.emplace(it, v) } -> std::same_as; +}; + +// constexpr iterator insert(const_iterator position, const T& x); +template +concept has_insert_const = + requires(T t, T::const_iterator it, T::const_reference x) { + { t.insert(it, x) } -> std::same_as; + }; + +// constexpr iterator insert(const_iterator position, T&& x); +template +concept has_insert_rv = requires(T t, T::const_iterator it, T::value_type &&x) { + { + t.insert(it, static_cast(x)) + } -> std::same_as; +}; + +// constexpr iterator insert(const_iterator position, size_type n, const T& x); +template +concept has_insert_size = + requires(T t, T::const_iterator it, T::size_type n, T::const_reference x) { + { t.insert(it, n, x) } -> std::same_as; + }; + +// constexpr iterator insert(const_iterator position, InputIterator first, +// InputIterator last); +template +concept has_insert_iterator = + requires(T t, T::const_iterator it, T::iterator first, T::iterator last) { + { t.insert(it, first, last) } -> std::same_as; + }; + +// InputIterator last); constexpr iterator insert_range(const_iterator position, +// R&& rg); +template +concept has_insert_range = requires(T t, T::const_iterator it, R &&r) { + { t.insert_range(it, r) } -> std::same_as; +}; + +// constexpr iterator insert(const_iterator position, initializer_list il); +template +concept has_insert_initializer = + requires(T t, typename T::const_iterator it, + std::initializer_list il) { + { t.insert(it, il) } -> std::same_as; + }; + +TEST(Freestanding, deleted) { + + using IV = beman::inplace_vector; + using FIV = beman::freestanding::inplace_vector; + + using range = std::array; + using input_iterator = std::istream_iterator; + using initializer_list = std::initializer_list; + + IV::value_type v; + + // constexpr explicit inplace_vector(size_type n); + static_assert(std::is_constructible_v); + static_assert(!std::is_constructible_v); + + // constexpr inplace_vector(size_type n, const T& value); + static_assert(std::is_constructible_v); + static_assert( + !std::is_constructible_v); + + // constexpr inplace_vector(InputIterator first, InputIterator last); + static_assert(std::is_constructible_v); + static_assert(!std::is_constructible_v); + + // constexpr inplace_vector(from_range_t, R&& rg); + static_assert(std::is_constructible_v); + static_assert(!std::is_constructible_v); + + // constexpr inplace_vector(initializer_list il); + static_assert(std::is_constructible_v); + static_assert(!std::is_constructible_v); + + // constexpr inplace_vector& operator=(initializer_list); + static_assert(std::is_assignable_v); + static_assert(!std::is_assignable_v); + + // constexpr void assign(InputIterator first, InputIterator last) + static_assert(has_assign_iterator); + static_assert(!has_assign_iterator); + + // constexpr void assign_range(R&& rg); + static_assert(has_assign_range); + static_assert(!has_assign_range); + + // constexpr void assign(size_type n, const T& u); + static_assert(has_assign_size); + static_assert(!has_assign_size); + + // constexpr void assign(initializer_list il); + static_assert(has_assign_initializer); + static_assert(!has_assign_initializer); + + // constexpr void resize(size_type sz); + static_assert(has_resize); + static_assert(!has_resize); + + // constexpr void resize(size_type sz, const T& c); + static_assert(has_resize_ref); + static_assert(!has_resize_ref); + + // void reserve(size_type n); + static_assert(has_reserve); + static_assert(!has_reserve); + + // constexpr reference at(size_type n); + static_assert(has_at); + static_assert(!has_at); + + // constexpr const_reference at(size_type n) const; + static_assert(has_const_at); + static_assert(!has_const_at); + + // emplace_back(Args&&... args); + static_assert(has_emplace_back); + static_assert(!has_emplace_back); + + // constexpr reference push_back(const T& x); + static_assert(has_push_back_const); + static_assert(!has_push_back_const); + + // constexpr reference push_back(T&& x); + static_assert(has_push_back_rv); + static_assert(!has_push_back_rv); + + // constexpr void append_range(R&& rg); + static_assert(has_append_range); + static_assert(!has_append_range); + + // constexpr iterator emplace(const_iterator position, Args&&... args); + static_assert(has_emplace); + static_assert(!has_emplace); + + // constexpr iterator insert(const_iterator position, const T& x); + static_assert(has_insert_const); + static_assert(!has_insert_const); + + // constexpr iterator insert(const_iterator position, T&& x); + static_assert(has_insert_rv); + static_assert(!has_insert_rv); + + // constexpr iterator insert(const_iterator position, size_type n, T& x); + static_assert(has_insert_size); + static_assert(!has_insert_size); + + // constexpr iterator insert(const_iterator position, InputIterator first, + static_assert(has_insert_iterator); + static_assert(!has_insert_iterator); + + // InputIterator last); constexpr iterator insert_range(const_iterator + // position, R&& rg); + static_assert(has_insert_range); + static_assert(!has_insert_range); + + // constexpr iterator insert(const_iterator position, initializer_list il); + static_assert(has_insert_initializer); + static_assert(!has_insert_initializer); + + EXPECT_TRUE(true); +} + +TEST(Freestanding, usage) { + using IV = beman::freestanding::inplace_vector; + using T = IV::value_type; + + IV device; + + EXPECT_EQ(device.size(), 0); + + for (auto i = 0; i < device.capacity(); ++i) { + const auto value = T{i}; + auto res = device.try_emplace_back(value); + EXPECT_NE(res, nullptr); + EXPECT_EQ(*res, value); + EXPECT_EQ(device.back(), value); + } + + EXPECT_EQ(device.size(), device.capacity()); + + EXPECT_EQ(nullptr, device.try_emplace_back(T{})); + + EXPECT_EQ(device.size(), device.capacity()); + + for (auto i = int(device.capacity()); i > 0; --i) { + const auto value = T{i - 1}; + EXPECT_EQ(device.back(), value); + device.pop_back(); + } + + device.pop_back(); // UB should be safe + + EXPECT_EQ(device.size(), 0); +}