From 1e49cda2d57aca4b1f7a207af19e483d054df324 Mon Sep 17 00:00:00 2001 From: Hui Date: Mon, 3 Jun 2024 19:27:46 +0100 Subject: [PATCH] SBO + Strong exception --- any_view.md | 4 + impl/any_view/any_view_te.hpp | 304 ++++-------- impl/any_view/storage.hpp | 180 +++++++ impl/any_view/test/iterator/bidirectional.cpp | 2 +- impl/any_view/test/iterator/exception.cpp | 120 +++++ impl/any_view/test/iterator/forward.cpp | 2 +- impl/any_view/test/iterator/input.cpp | 2 +- impl/any_view/test/iterator/input_common.cpp | 2 +- impl/any_view/test/iterator/random_access.cpp | 2 +- impl/any_view/test/storage/storage.cpp | 465 ++++++++++++++++++ impl/any_view/test/view/bidirectional.cpp | 2 +- impl/any_view/test/view/contiguous.cpp | 2 +- impl/any_view/test/view/forward.cpp | 2 +- impl/any_view/test/view/input.cpp | 2 +- impl/any_view/test/view/random_access.cpp | 2 +- 15 files changed, 868 insertions(+), 225 deletions(-) create mode 100644 impl/any_view/storage.hpp create mode 100644 impl/any_view/test/iterator/exception.cpp create mode 100644 impl/any_view/test/storage/storage.cpp diff --git a/any_view.md b/any_view.md index 2e78fa0..37e0d60 100644 --- a/any_view.md +++ b/any_view.md @@ -198,6 +198,10 @@ class any_view; - const-iteratable will make the design super complicated as all the types can be different between const and non-const. +- TODO: remove constexpr due to SBO +- TODO: move ctor cannot guarentee move ctors have been called +- TODO: view can be valueless: because strong exception guarentee + if we want to support move (or move-only) + # Implementation Experience - Reference implementation in the repo diff --git a/impl/any_view/any_view_te.hpp b/impl/any_view/any_view_te.hpp index f086976..6e671d5 100644 --- a/impl/any_view/any_view_te.hpp +++ b/impl/any_view/any_view_te.hpp @@ -5,6 +5,9 @@ #include #include #include + +#include "storage.hpp" + namespace std::ranges { inline namespace __any_view { @@ -53,6 +56,10 @@ class any_view { static constexpr category Traversal = Cat & category::category_mask; static constexpr bool is_common = (Cat & category::common) == category::common; + static constexpr bool is_view_copyable = + (Cat & category::move_only_view) == category::none; + static constexpr bool is_iterator_copyable = + Traversal >= category::forward || is_common; template struct maybe_t : T {}; @@ -60,43 +67,32 @@ class any_view { template struct maybe_t {}; - struct destructor_vtable { - void (*destructor_)(void *); - }; - - struct movable_vtable { - void *(*move_)(void *); - }; - - struct copyable_vtable { - void *(*copy_)(const void *); - }; + using iterator_storage = + detail::storage<3 * sizeof(void *), sizeof(void *), is_iterator_copyable>; - struct basic_input_iterator_vtable : destructor_vtable, movable_vtable { - Ref (*deref_)(const void *); - void (*increment_)(void *); - RValueRef (*iter_move_)(const void *); + struct basic_input_iterator_vtable { + Ref (*deref_)(const iterator_storage &); + void (*increment_)(iterator_storage &); + RValueRef (*iter_move_)(const iterator_storage &); }; struct equality_vtable { - bool (*equal_)(const void *, const void *); + bool (*equal_)(const iterator_storage &, const iterator_storage &); }; struct common_input_iterator : basic_input_iterator_vtable, - copyable_vtable, equality_vtable {}; struct forward_iterator_vtable : basic_input_iterator_vtable, - copyable_vtable, equality_vtable {}; struct bidirectional_iterator_vtable : forward_iterator_vtable { - void (*decrement_)(void *); + void (*decrement_)(iterator_storage &); }; struct random_access_iterator_vtable : bidirectional_iterator_vtable { - void (*advance_)(void *, Diff); - Diff (*distance_to_)(const void *, const void *); + void (*advance_)(iterator_storage &, Diff); + Diff (*distance_to_)(const iterator_storage &, const iterator_storage &); }; // for contiguous , we just return raw pointers @@ -114,39 +110,17 @@ class any_view { conditional_t>>>>; - struct basic_vtable_gen { - template - static constexpr void *move(void *self) { - return new T(std::move(*(static_cast(self)))); - } - - template - static constexpr void *copy(const void *self) { - return new T(*(static_cast(self))); - } - - // TODO: possibly need both destroy (without free to support SBO) and delete - template - static constexpr void destroy(void *self) { - // return std::destroy_at(static_cast(self)); - delete static_cast(self); - } - }; - - struct iterator_vtable_gen : basic_vtable_gen { + struct iterator_vtable_gen { template static constexpr auto generate() { any_iterator_vtable t; if constexpr (Traversal != category::contiguous) { - t.move_ = &basic_vtable_gen::template move; - t.destructor_ = &basic_vtable_gen::template destroy; t.deref_ = &deref; t.increment_ = &increment; t.iter_move_ = &iter_move; if constexpr (Traversal >= category::forward || is_common) { - t.copy_ = &basic_vtable_gen::template copy; t.equal_ = &equal; } @@ -165,46 +139,47 @@ class any_view { // input template - static constexpr Ref deref(const void *self) { - return **(static_cast(self)); + static constexpr Ref deref(const iterator_storage &self) { + return **(self.template get_ptr()); }; template - static constexpr void increment(void *self) { - ++(*(static_cast(self))); + static constexpr void increment(iterator_storage &self) { + ++(*(self.template get_ptr())); }; template - static constexpr RValueRef iter_move(const void *self) { - return std::ranges::iter_move(*(static_cast(self))); + static constexpr RValueRef iter_move(const iterator_storage &self) { + return std::ranges::iter_move(*(self.template get_ptr())); }; // forward template - static constexpr bool equal(const void *self, const void *other) { - return (*static_cast(self)) == - (*static_cast(other)); + static constexpr bool equal(const iterator_storage &lhs, + const iterator_storage &rhs) { + return *lhs.template get_ptr() == *rhs.template get_ptr(); } // bidi template - static constexpr void decrement(void *self) { - --(*(static_cast(self))); + static constexpr void decrement(iterator_storage &self) { + --(*(self.template get_ptr())); } // random access template - static constexpr void advance(void *self, Diff diff) { - (*static_cast(self)) += diff; + static constexpr void advance(iterator_storage &self, Diff diff) { + (*self.template get_ptr()) += diff; } template - static constexpr Diff distance_to(const void *self, const void *other) { - return Diff((*static_cast(self)) - - (*static_cast(other))); + static constexpr Diff distance_to(const iterator_storage &self, + const iterator_storage &other) { + return Diff((*self.template get_ptr()) - + (*other.template get_ptr())); } }; @@ -248,54 +223,19 @@ class any_view { constexpr any_iterator() = default; - constexpr any_iterator(const any_iterator &other) - requires(Traversal >= category::forward || is_common) - : iter_vtable_(other.iter_vtable_) { - if (!other.is_singular()) { - iter_ = (*(other.iter_vtable_->copy_))(other.iter_); - } - } + constexpr any_iterator(const any_iterator &) + requires is_iterator_copyable + = default; - constexpr any_iterator(any_iterator &&other) - : iter_vtable_(other.iter_vtable_) { - if (!other.is_singular()) { - iter_ = (*(other.iter_vtable_->move_))(other.iter_); - } - } + constexpr any_iterator(any_iterator &&) = default; - constexpr any_iterator &operator=(const any_iterator &other) - requires(Traversal >= category::forward || is_common) - { - if (this != &other) { - if (!is_singular()) { - (*(iter_vtable_->destructor_))(iter_); - } - if (!other.is_singular()) { - iter_ = (*(other.iter_vtable_->copy_))(other.iter_); - } - iter_vtable_ = other.iter_vtable_; - } - return *this; - } + constexpr any_iterator &operator=(const any_iterator &) + requires is_iterator_copyable + = default; - constexpr any_iterator &operator=(any_iterator &&other) { - if (this != &other) { - if (!is_singular()) { - (*(iter_vtable_->destructor_))(iter_); - } - if (!other.is_singular()) { - iter_ = (*(other.iter_vtable_->move_))(other.iter_); - } - iter_vtable_ = other.iter_vtable_; - } - return *this; - } + constexpr any_iterator &operator=(any_iterator &&) = default; - constexpr ~any_iterator() { - if (!is_singular()) { - (*(iter_vtable_->destructor_))(iter_); - } - } + constexpr ~any_iterator() = default; constexpr Ref operator*() const { assert(!is_singular()); @@ -434,33 +374,32 @@ class any_view { // private: const any_iterator_vtable *iter_vtable_ = nullptr; - void *iter_ = nullptr; + iterator_storage iter_; template constexpr any_iterator(const any_iterator_vtable *table, Iter iter) - : iter_vtable_(table), iter_(new Iter(std::move(iter))) {} + : iter_vtable_(table), iter_(detail::type{}, std::move(iter)) {} - constexpr bool is_singular() const { return !iter_vtable_; } + constexpr bool is_singular() const { return iter_.is_singular(); } }; using iterator = std::conditional_t, any_iterator>; - struct sentinel_vtable : movable_vtable, copyable_vtable, destructor_vtable { + using sentinel_storage = + detail::storage<3 * sizeof(void *), sizeof(void *), true>; + struct sentinel_vtable { bool (*equal_)(const iterator &, const any_sentinel &); }; struct any_sentinel_vtable : maybe_t {}; - struct sentinel_vtable_gen : basic_vtable_gen { + struct sentinel_vtable_gen { template static constexpr auto generate() { any_sentinel_vtable t; if constexpr (!is_common) { - t.move_ = &basic_vtable_gen::template move; - t.copy_ = &basic_vtable_gen::template copy; - t.destructor_ = &basic_vtable_gen::template destroy; t.equal_ = &equal; } @@ -472,11 +411,11 @@ class any_view { const any_sentinel &sent) { if (sent.is_singular()) return false; if constexpr (Traversal == category::contiguous) { - return iter == *static_cast(sent.sent_); + return iter == *sent.sent_.template get_ptr(); } else { if (iter.is_singular()) return false; - return *static_cast(iter.iter_) == - *static_cast(sent.sent_); + return *(iter.iter_.template get_ptr()) == + *(sent.sent_.template get_ptr()); } } }; @@ -484,51 +423,15 @@ class any_view { struct any_sentinel { constexpr any_sentinel() = default; - constexpr any_sentinel(const any_sentinel &other) - : sent_vtable_(other.sent_vtable_) { - if (!other.is_singular()) { - sent_ = (*(other.sent_vtable_->copy_))(other.sent_); - } - } + constexpr any_sentinel(const any_sentinel &) = default; - constexpr any_sentinel(any_sentinel &&other) - : sent_vtable_(other.sent_vtable_) { - if (!other.is_singular()) { - sent_ = (*(other.sent_vtable_->move_))(other.sent_); - } - } + constexpr any_sentinel(any_sentinel &&) = default; - constexpr any_sentinel &operator=(const any_sentinel &other) { - if (this != &other) { - if (!is_singular()) { - (*(sent_vtable_->destructor_))(sent_); - } - if (!other.is_singular()) { - sent_ = (*(other.sent_vtable_->copy_))(other.sent_); - } - sent_vtable_ = other.sent_vtable_; - } - return *this; - } + constexpr any_sentinel &operator=(const any_sentinel &) = default; - constexpr any_sentinel &operator=(any_sentinel &&other) { - if (this != &other) { - if (!is_singular()) { - (*(sent_vtable_->destructor_))(sent_); - } - if (!other.is_singular()) { - sent_ = (*(other.sent_vtable_->move_))(other.sent_); - } - sent_vtable_ = other.sent_vtable_; - } - return *this; - } + constexpr any_sentinel &operator=(any_sentinel &&) = default; - constexpr ~any_sentinel() { - if (!is_singular()) { - (*(sent_vtable_->destructor_))(sent_); - } - } + constexpr ~any_sentinel() = default; friend constexpr bool operator==(const iterator &iter, const any_sentinel &sent) { @@ -537,45 +440,34 @@ class any_view { // private: const any_sentinel_vtable *sent_vtable_ = nullptr; - void *sent_ = nullptr; + sentinel_storage sent_; template constexpr any_sentinel(const any_sentinel_vtable *table, Sent sent) - : sent_vtable_(table), sent_(new Sent(std::move(sent))) {} + : sent_vtable_(table), sent_(detail::type{}, std::move(sent)) {} - constexpr bool is_singular() const { return !sent_vtable_; } + constexpr bool is_singular() const { return sent_.is_singular(); } }; using sentinel = std::conditional_t; + using view_storage = + detail::storage<3 * sizeof(void *), sizeof(void *), is_view_copyable>; struct sized_vtable { - std::size_t (*size_)(const void *); + std::size_t (*size_)(const view_storage &); }; - struct any_view_vtable - : destructor_vtable, - movable_vtable, - maybe_t, - maybe_t { - iterator (*begin_)(void *); - sentinel (*end_)(void *); + : maybe_t { + iterator (*begin_)(view_storage &); + sentinel (*end_)(view_storage &); }; - struct view_vtable_gen : basic_vtable_gen { + struct view_vtable_gen { template static constexpr auto generate() { any_view_vtable t; - t.move_ = &basic_vtable_gen::template move; - t.destructor_ = &basic_vtable_gen::template destroy; - t.begin_ = &begin; t.end_ = &end; - - if constexpr ((Cat & category::move_only_view) == category::none) { - t.copy_ = &basic_vtable_gen::template copy; - } - if constexpr ((Cat & category::sized) != category::none) { t.size_ = &size; } @@ -584,8 +476,8 @@ class any_view { } template - static constexpr iterator begin(void *v) { - auto &view = *(static_cast(v)); + static constexpr iterator begin(view_storage &v) { + auto &view = *(v.template get_ptr()); if constexpr (Traversal == category::contiguous) { return std::ranges::begin(view); } else { @@ -595,8 +487,8 @@ class any_view { } template - static constexpr sentinel end(void *v) { - auto &view = *(static_cast(v)); + static constexpr sentinel end(view_storage &v) { + auto &view = *(v.template get_ptr()); if constexpr (Traversal == category::contiguous && is_common) { return std::ranges::end(view); } else if constexpr (is_common) { @@ -610,8 +502,8 @@ class any_view { } template - static constexpr std::size_t size(const void *view) { - return std::ranges::size(*(static_cast(view))); + static constexpr std::size_t size(const view_storage &v) { + return std::ranges::size(*(v.template get_ptr())); } }; @@ -622,8 +514,7 @@ class any_view { return false; } - if constexpr ((Cat & category::common) != category::none && - !std::ranges::common_range) { + if constexpr (is_common && !std::ranges::common_range) { return false; } @@ -632,8 +523,7 @@ class any_view { return false; } - if constexpr ((Cat & category::move_only_view) == category::none && - !std::copyable) { + if constexpr (is_view_copyable && !std::copyable) { return false; } constexpr auto cat_mask = Cat & category::category_mask; @@ -654,38 +544,22 @@ class any_view { requires(!std::same_as && std::ranges::view && view_category_constraint()) constexpr any_view(View view) - : view_vtable_(&view_vtable), view_(new View(std::move(view))) {} + : view_vtable_(&view_vtable), + view_(detail::type{}, std::move(view)) {} - constexpr any_view(const any_view &other) - requires((Cat & category::move_only_view) == category::none) - : view_vtable_(other.view_vtable_), - view_((*(view_vtable_->copy_))(other.view_)) {} + constexpr any_view(const any_view &) + requires is_view_copyable + = default; - constexpr any_view(any_view &&other) - : view_vtable_(other.view_vtable_), - view_((*(view_vtable_->move_))(other.view_)) {} + constexpr any_view(any_view &&) = default; - constexpr any_view &operator=(const any_view &other) - requires((Cat & category::move_only_view) == category::none) - { - if (this != &other) { - (*(view_vtable_->destructor_))(view_); - view_ = (*(other.view_vtable_->copy_))(other.view_); - view_vtable_ = other.view_vtable_; - } - return *this; - } + constexpr any_view &operator=(const any_view &) + requires is_view_copyable + = default; - constexpr any_view &operator=(any_view &&other) { - if (this != &other) { - (*(view_vtable_->destructor_))(view_); - view_ = (*(other.view_vtable_->move_))(other.view_); - view_vtable_ = other.view_vtable_; - } - return *this; - } + constexpr any_view &operator=(any_view &&) = default; - constexpr ~any_view() { (*(view_vtable_->destructor_))(view_); } + constexpr ~any_view() = default; constexpr iterator begin() { return (*(view_vtable_->begin_))(view_); } constexpr sentinel end() { return (*(view_vtable_->end_))(view_); } @@ -710,7 +584,7 @@ class any_view { view_vtable_gen::template generate(); const any_view_vtable *view_vtable_; - void *view_; + view_storage view_; }; template diff --git a/impl/any_view/storage.hpp b/impl/any_view/storage.hpp new file mode 100644 index 0000000..ba506fb --- /dev/null +++ b/impl/any_view/storage.hpp @@ -0,0 +1,180 @@ +#ifndef LIBCPP__RANGE_STORAGE_HPP +#define LIBCPP__RANGE_STORAGE_HPP + +#include +#include + +namespace std::ranges::detail { + +template +struct type { + using t = T; +}; + +template +struct storage { + constexpr storage() = default; + + template + requires constructible_from + constexpr storage(type, Args &&...args) : vtable_(&vtable_instance) { + if constexpr (use_small_buffer) { + std::construct_at(get_ptr(), std::forward(args)...); + } else { + heap_ptr_ = new T(std::forward(args)...); + } + } + + constexpr storage(const storage &other) + requires Copyable + { + if (!other.is_singular()) { + (*(other.vtable_->copy_))(other, *this); + } + } + + constexpr storage(storage &&other) noexcept { + if (!other.is_singular()) { + (*(other.vtable_->move_))(std::move(other), *this); + } + } + + constexpr storage &operator=(const storage &other) + requires Copyable + { + storage(other).swap(*this); + return *this; + } + + constexpr storage &operator=(storage &&other) noexcept + { + storage(std::move(other)).swap(*this); + return *this; + } + + constexpr ~storage() { + if (!is_singular()) { + (*(vtable_->destroy_))(*this); + } + } + + template + constexpr decltype(auto) get_ptr(this Self &&self) { + constexpr bool is_this_const = + std::is_const_v>; + using ptr = std::conditional_t; + if constexpr (use_small_buffer) { + return reinterpret_cast(&self.buf_.buf_[0]); + } else { + return static_cast(self.heap_ptr_); + } + } + + constexpr void swap(storage &other) noexcept { + if (this == &other) return; + + if (!is_singular() && !other.is_singular()) { + storage tmp(singular_tag{}); + (*other.vtable_->destructive_move_)(std::move(other), tmp); + (*vtable_->destructive_move_)(std::move(*this), other); + (*tmp.vtable_->destructive_move_)(std::move(tmp), *this); + tmp.vtable_ = nullptr; + } else if (!is_singular()) { + (*vtable_->destructive_move_)(std::move(*this), other); + vtable_ = nullptr; + } else if (!other.is_singular()) { + (*other.vtable_->destructive_move_)(std::move(other), *this); + other.vtable_ = nullptr; + } + } + + constexpr bool is_singular() const { return !vtable_; } + + template + static constexpr bool unittest_is_small() { + return use_small_buffer; + } + + private: + struct singular_tag {}; + explicit constexpr storage(singular_tag) {}; + + struct Buffer { + static constexpr size_t size = Size; + alignas(Align) char buf_[size]; + }; + + template + static constexpr bool use_small_buffer = + sizeof(Tp) <= sizeof(Buffer) && alignof(Buffer) % alignof(Tp) == 0 && + is_nothrow_move_constructible_v; + + union { + void *heap_ptr_; + Buffer buf_; + }; + + struct empty {}; + struct copyable_vtable { + void (*copy_)(const storage &, storage &); + }; + struct vtable : conditional_t { + void (*destroy_)(storage &); + void (*move_)(storage &&, storage &); + void (*destructive_move_)(storage &&, storage &); + }; + + vtable const *vtable_ = nullptr; + + template + consteval static vtable gen_vtable() { + vtable vt{}; + if constexpr (use_small_buffer) { + vt.destroy_ = [](storage &self) noexcept { + std::destroy_at(self.get_ptr()); + }; + vt.move_ = [](storage &&self, storage &dest) noexcept { + std::construct_at(dest.get_ptr(), std::move((*self.get_ptr()))); + dest.vtable_ = self.vtable_; + }; + vt.destructive_move_ = [](storage &&self, storage &dest) noexcept { + std::construct_at(dest.get_ptr(), std::move((*self.get_ptr()))); + dest.vtable_ = self.vtable_; + std::destroy_at(self.get_ptr()); + }; + if constexpr (Copyable) { + // may throw, but self is unchanged after throw + vt.copy_ = [](storage const &self, storage &dest) { + std::construct_at(dest.get_ptr(), *self.get_ptr()); + dest.vtable_ = self.vtable_; + }; + } + } else { + vt.destroy_ = [](storage &self) noexcept { + delete static_cast(self.heap_ptr_); + }; + vt.move_ = [](storage &&self, storage &dest) noexcept { + dest.heap_ptr_ = self.heap_ptr_; + dest.vtable_ = self.vtable_; + self.heap_ptr_ = nullptr; + self.vtable_ = nullptr; + }; + vt.destructive_move_ = vt.move_; + if constexpr (Copyable) { + vt.copy_ = [](storage const &self, storage &dest) { + // may throw, but self is unchanged after throw + dest.heap_ptr_ = new Tp(*static_cast(self.heap_ptr_)); + dest.vtable_ = self.vtable_; + }; + } + } + return vt; + } + + template + static constexpr vtable vtable_instance = gen_vtable(); +}; + +} // namespace std::ranges::detail + +#endif diff --git a/impl/any_view/test/iterator/bidirectional.cpp b/impl/any_view/test/iterator/bidirectional.cpp index e4cd7c2..3c27f0f 100644 --- a/impl/any_view/test/iterator/bidirectional.cpp +++ b/impl/any_view/test/iterator/bidirectional.cpp @@ -145,7 +145,7 @@ constexpr bool test() { TEST_POINT("forward") { test(); - static_assert(test()); + // static_assert(test()); } } // namespace diff --git a/impl/any_view/test/iterator/exception.cpp b/impl/any_view/test/iterator/exception.cpp new file mode 100644 index 0000000..c5bbc45 --- /dev/null +++ b/impl/any_view/test/iterator/exception.cpp @@ -0,0 +1,120 @@ +#include + +#include "any_view.hpp" + +using AnyView = std::ranges::any_view; +using Iter = std::ranges::iterator_t; + +struct ThrowIterator { + using It = int*; + It it; + + constexpr ThrowIterator() = default; + constexpr ThrowIterator(int* i) : it(i) {} + constexpr ThrowIterator(const ThrowIterator& other) : it(other.it) { + throw 5; + } + constexpr ThrowIterator(ThrowIterator&& other) : it(std::move(other.it)) { + if (MoveThrow) throw 6; + } + constexpr ThrowIterator& operator=(const ThrowIterator&) = default; + constexpr ThrowIterator& operator=(ThrowIterator&&) = default; + + int& operator*() const { return *it; } + + ThrowIterator& operator++() { + ++it; + return *this; + } + ThrowIterator operator++(int) { return {it++}; } + + bool operator==(ThrowIterator other) const { return it == other.it; } + + using value_type = int; // to model indirectly_readable_traits + using difference_type = std::ptrdiff_t; // to model incrementable_traits + + static inline bool MoveThrow = false; +}; + +template +struct BufferView : std::ranges::view_base { + using T = std::iter_value_t; + T* data_; + std::size_t size_; + + template + constexpr BufferView(T (&b)[N]) : data_(b), size_(N) {} + constexpr BufferView(T* p, std::size_t s) : data_(p), size_(s) {} + + constexpr NonConstIter begin() + requires(!std::is_same_v) + { + return NonConstIter(this->data_); + } + constexpr Iter begin() const { return Iter(this->data_); } + + constexpr NonConstSent end() + requires(!std::is_same_v) + { + if constexpr (std::is_same_v) { + return NonConstIter(this->data_ + this->size_); + } else { + return NonConstSent(NonConstIter(this->data_ + this->size_)); + } + } + + constexpr Sent end() const { + if constexpr (std::is_same_v) { + return Iter(this->data_ + this->size_); + } else { + return Sent(Iter(this->data_ + this->size_)); + } + } +}; + +constexpr void copy_exception() { + int a[] = {1, 2, 3}; + using View = BufferView; + AnyView av{View{a}}; + Iter iter1 = av.begin(); + ++iter1; + Iter iter2 = av.begin(); + + try { + iter2 = iter1; + assert(false); + } catch (int i) { + assert(i == 5); + } + assert(*iter1 == 2); + assert(*iter2 == 1); +} + +constexpr void move_exception() { + int a[] = {1, 2, 3}; + using View = BufferView; + AnyView av{View{a}}; + Iter iter1 = av.begin(); + ++iter1; + Iter iter2 = av.begin(); + + ThrowIterator::MoveThrow = true; + + // won't throw because there should be no move happening + iter2 = std::move(iter1); + + assert(*iter2 == 2); + assert(iter1.is_singular()); +} + +constexpr bool test() { + copy_exception(); + move_exception(); + return true; +} + +int main() { + test(); + // static_assert(test()); +} diff --git a/impl/any_view/test/iterator/forward.cpp b/impl/any_view/test/iterator/forward.cpp index ebfe73f..eba7e55 100644 --- a/impl/any_view/test/iterator/forward.cpp +++ b/impl/any_view/test/iterator/forward.cpp @@ -134,7 +134,7 @@ constexpr bool test() { TEST_POINT("forward") { test(); - static_assert(test()); + // static_assert(test()); } } // namespace diff --git a/impl/any_view/test/iterator/input.cpp b/impl/any_view/test/iterator/input.cpp index 77ee842..c809d96 100644 --- a/impl/any_view/test/iterator/input.cpp +++ b/impl/any_view/test/iterator/input.cpp @@ -79,7 +79,7 @@ constexpr bool test() { TEST_POINT("input") { test(); - static_assert(test()); + // static_assert(test()); } } // namespace diff --git a/impl/any_view/test/iterator/input_common.cpp b/impl/any_view/test/iterator/input_common.cpp index 31c70fd..0c7346c 100644 --- a/impl/any_view/test/iterator/input_common.cpp +++ b/impl/any_view/test/iterator/input_common.cpp @@ -116,7 +116,7 @@ constexpr bool test() { TEST_POINT("input") { test(); - static_assert(test()); + // static_assert(test()); } } // namespace diff --git a/impl/any_view/test/iterator/random_access.cpp b/impl/any_view/test/iterator/random_access.cpp index 9fbad1c..0e8ff99 100644 --- a/impl/any_view/test/iterator/random_access.cpp +++ b/impl/any_view/test/iterator/random_access.cpp @@ -227,7 +227,7 @@ constexpr bool test() { TEST_POINT("forward") { test(); - static_assert(test()); + // static_assert(test()); } } // namespace diff --git a/impl/any_view/test/storage/storage.cpp b/impl/any_view/test/storage/storage.cpp new file mode 100644 index 0000000..03123ba --- /dev/null +++ b/impl/any_view/test/storage/storage.cpp @@ -0,0 +1,465 @@ +#include +#include +#include +#include + +#include "storage.hpp" + +#define TEST_POINT(x) TEST_CASE(x, "[storage]") + +namespace { + +using Storage = + std::ranges::detail::storage<3 * sizeof(void*), sizeof(void*), true>; + +using std::ranges::detail::type; + +using Small = int; + +struct Big { + constexpr Big(int ii) : i(ii) {} + + constexpr operator int() const { return i; } + int i; + char c[100] = {}; +}; + +struct Stats { + int construct = 0; + int copy_construct = 0; + int copy_assignment = 0; + int move_construct = 0; + int move_assignment = 0; + int destroy = 0; +}; + +template +struct alignas(alignof(void*)) Track { + Stats* stats_; + T t_; + + template + requires std::constructible_from + constexpr Track(Stats& stats, Args&&... args) + : stats_(&stats), t_(std::forward(args)...) { + ++stats_->construct; + } + + constexpr Track(const Track& t) : stats_(t.stats_), t_(t.t_) { + ++stats_->copy_construct; + } + + constexpr Track(Track&& t) noexcept(std::is_nothrow_move_constructible_v) + : stats_(t.stats_), t_(std::move(t.t_)) { + ++stats_->move_construct; + } + + constexpr Track& operator=(const Track& other) { + stats_ = other.stats_; + t_ = other.t_; + ++stats_->copy_assignment; + return *this; + } + + constexpr Track& operator=(Track&& other) noexcept( + std::is_nothrow_move_assignable_v) { + stats_ = other.stats_; + t_ = std::move(other.t_); + ++stats_->move_assignment; + return *this; + } + + constexpr ~Track() { ++stats_->destroy; } +}; + +static_assert(Storage::unittest_is_small()); +static_assert(!Storage::unittest_is_small()); +static_assert(Storage::unittest_is_small>()); +static_assert(!Storage::unittest_is_small>()); + +constexpr void singular() { + Storage s; + assert(s.is_singular()); +} + +template +constexpr void basic() { + Storage s{type{}, 5}; + assert(*s.get_ptr() == 5); +} + +template +constexpr void copy() { + // non singular + { + Storage s1{type{}, 5}; + Storage s2{s1}; + + assert(*s1.get_ptr() == 5); + assert(*s2.get_ptr() == 5); + } + + // singular + { + Storage s1{}; + auto s2 = s1; + + assert(s1.is_singular()); + assert(s2.is_singular()); + } + + // track + { + Stats stats{}; + { + Storage s1{type>{}, stats, 5}; + assert(stats.construct == 1); + assert(stats.copy_construct == 0); + assert(stats.move_construct == 0); + assert(stats.destroy == 0); + + assert(s1.get_ptr>()->t_ == 5); + + Storage s2{s1}; + assert(stats.construct == 1); + assert(stats.copy_construct == 1); + assert(stats.move_construct == 0); + assert(stats.destroy == 0); + + assert(s2.get_ptr>()->t_ == 5); + } + assert(stats.destroy == 2); + } +} + +template +constexpr void move() { + // non singular + { + Storage s1{type{}, 5}; + Storage s2{std::move(s1)}; + + if constexpr (Storage::unittest_is_small()) { + assert(!s1.is_singular()); + assert(*s1.get_ptr() == 5); + } else { + assert(s1.is_singular()); + } + assert(!s2.is_singular()); + assert(*s2.get_ptr() == 5); + } + + // singular + { + Storage s1{}; + auto s2 = std::move(s1); + + assert(s1.is_singular()); + assert(s2.is_singular()); + } + // track + { + Stats stats{}; + { + Storage s1{type>{}, stats, 5}; + assert(stats.construct == 1); + assert(stats.copy_construct == 0); + assert(stats.move_construct == 0); + assert(stats.destroy == 0); + + assert(s1.get_ptr>()->t_ == 5); + + Storage s2{std::move(s1)}; + assert(stats.construct == 1); + assert(stats.copy_construct == 0); + + auto expected_move = Storage::unittest_is_small>() ? 1 : 0; + assert(stats.move_construct == expected_move); + assert(stats.destroy == 0); + + assert(s2.get_ptr>()->t_ == 5); + } + auto expected_destroy = Storage::unittest_is_small>() ? 2 : 1; + assert(stats.destroy == expected_destroy); + } +} + +template +constexpr void move_assignment() { + constexpr bool lhsSmall = Storage::unittest_is_small(); + constexpr bool rhsSmall = Storage::unittest_is_small(); + // non singular + { + Storage s1{type{}, 5}; + Storage s2{type{}, 6}; + + s1 = std::move(s2); + assert(*s1.get_ptr() == 6); + if constexpr (rhsSmall) { + assert(*s2.get_ptr() == 6); + } else { + assert(s2.is_singular()); + } + } + + // lhs singular + { + Storage s1{}; + Storage s2{type{}, 6}; + + s1 = std::move(s2); + assert(*s1.get_ptr() == 6); + if constexpr (rhsSmall) { + assert(*s2.get_ptr() == 6); + } else { + assert(s2.is_singular()); + } + } + + // rhs singular + { + Storage s1{type{}, 5}; + Storage s2{}; + + s1 = std::move(s2); + assert(s1.is_singular()); + assert(s2.is_singular()); + } + + // track + { + Stats stats1{}; + Stats stats2{}; + { + Storage s1{type>{}, stats1, 5}; + assert(stats1.construct == 1); + assert(stats1.copy_construct == 0); + assert(stats1.move_construct == 0); + assert(stats1.destroy == 0); + + assert(s1.get_ptr>()->t_ == 5); + + Storage s2{type>{}, stats2, 6}; + assert(stats2.construct == 1); + assert(stats2.copy_construct == 0); + assert(stats2.move_construct == 0); + assert(stats2.destroy == 0); + + assert(s2.get_ptr>()->t_ == 6); + + s1 = std::move(s2); + + assert(stats1.construct == 1); + assert(stats1.copy_construct == 0); + assert(stats1.copy_assignment == 0); + assert(stats1.move_assignment == 0); + + assert(stats2.construct == 1); + assert(stats2.copy_construct == 0); + assert(stats2.copy_assignment == 0); + assert(stats2.move_assignment == 0); + + int expected_move_construct1 = 0; + int expected_destroy1 = 0; + int expected_move_construct2 = 0; + int expected_destroy2 = 0; + + if (lhsSmall && rhsSmall) { + expected_move_construct1 = 2; + expected_destroy1 = 3; + expected_move_construct2 = 2; + expected_destroy2 = 1; + } else if (lhsSmall) { + expected_move_construct1 = 2; + expected_destroy1 = 3; + expected_move_construct2 = 0; + expected_destroy2 = 0; + } else if (rhsSmall) { + expected_move_construct1 = 0; + expected_destroy1 = 1; + expected_move_construct2 = 2; + expected_destroy2 = 1; + } else { + expected_move_construct1 = 0; + expected_destroy1 = 1; + expected_move_construct2 = 0; + expected_destroy2 = 0; + } + assert(stats2.move_construct == expected_move_construct2); + assert(stats2.destroy == expected_destroy2); + + assert(stats1.move_construct == expected_move_construct1); + assert(stats1.destroy == expected_destroy1); + } + + int total_expected_destroy1 = 0; + int total_expected_destroy2 = 0; + if (lhsSmall && rhsSmall) { + total_expected_destroy1 = 3; + total_expected_destroy2 = 3; + } else if (lhsSmall) { + total_expected_destroy1 = 3; + total_expected_destroy2 = 1; + } else if (rhsSmall) { + total_expected_destroy1 = 1; + total_expected_destroy2 = 3; + } else { + total_expected_destroy1 = 1; + total_expected_destroy2 = 1; + } + assert(stats1.destroy == total_expected_destroy1); + assert(stats2.destroy == total_expected_destroy2); + } +} + +template +constexpr void copy_assignment() { + // non singular + { + Storage s1{type{}, 5}; + Storage s2{type{}, 6}; + + s1 = s2; + assert(*s1.get_ptr() == 6); + assert(*s2.get_ptr() == 6); + } + + // lhs singular + { + Storage s1{}; + Storage s2{type{}, 6}; + + s1 = s2; + assert(*s1.get_ptr() == 6); + assert(*s2.get_ptr() == 6); + } + + // rhs singular + { + Storage s1{type{}, 5}; + Storage s2{}; + + s1 = s2; + assert(s1.is_singular()); + assert(s2.is_singular()); + } + + // track + { + Stats stats1{}; + Stats stats2{}; + bool lhsSmall = Storage::unittest_is_small>(); + bool rhsSmall = Storage::unittest_is_small>(); + { + Storage s1{type>{}, stats1, 5}; + assert(stats1.construct == 1); + assert(stats1.copy_construct == 0); + assert(stats1.move_construct == 0); + assert(stats1.destroy == 0); + + assert(s1.get_ptr>()->t_ == 5); + + Storage s2{type>{}, stats2, 6}; + assert(stats2.construct == 1); + assert(stats2.copy_construct == 0); + assert(stats2.move_construct == 0); + assert(stats2.destroy == 0); + + assert(s2.get_ptr>()->t_ == 6); + + s1 = s2; + + assert(stats1.construct == 1); + assert(stats1.copy_construct == 0); + assert(stats1.copy_assignment == 0); + assert(stats1.move_assignment == 0); + + assert(stats2.construct == 1); + assert(stats2.copy_construct == 1); + assert(stats2.copy_assignment == 0); + assert(stats2.move_assignment == 0); + + int expected_move_construct1 = 0; + int expected_destroy1 = 0; + int expected_move_construct2 = 0; + int expected_destroy2 = 0; + + if (lhsSmall && rhsSmall) { + expected_move_construct1 = 2; + expected_destroy1 = 3; + expected_move_construct2 = 1; + expected_destroy2 = 1; + } else if (lhsSmall) { + expected_move_construct1 = 2; + expected_destroy1 = 3; + expected_move_construct2 = 0; + expected_destroy2 = 0; + } else if (rhsSmall) { + expected_move_construct1 = 0; + expected_destroy1 = 1; + expected_move_construct2 = 1; + expected_destroy2 = 1; + } else { + expected_move_construct1 = 0; + expected_destroy1 = 1; + expected_move_construct2 = 0; + expected_destroy2 = 0; + } + assert(stats2.move_construct == expected_move_construct2); + assert(stats2.destroy == expected_destroy2); + + assert(stats1.move_construct == expected_move_construct1); + assert(stats1.destroy == expected_destroy1); + } + + int total_expected_destroy1 = 0; + int total_expected_destroy2 = 0; + if (lhsSmall && rhsSmall) { + total_expected_destroy1 = 3; + total_expected_destroy2 = 3; + } else if (lhsSmall) { + total_expected_destroy1 = 3; + total_expected_destroy2 = 2; + } else if (rhsSmall) { + total_expected_destroy1 = 1; + total_expected_destroy2 = 3; + } else { + total_expected_destroy1 = 1; + total_expected_destroy2 = 2; + } + assert(stats1.destroy == total_expected_destroy1); + assert(stats2.destroy == total_expected_destroy2); + } +} + +[[maybe_unused]] constexpr bool test() { + singular(); + basic(); + copy(); + move(); + copy_assignment(); + move_assignment(); + return true; +} + +void non_constexpr_test() { + // type punning in small buffer not allowed in constexpr + basic(); + copy(); + move(); + copy_assignment(); + copy_assignment(); + copy_assignment(); + move_assignment(); + move_assignment(); + move_assignment(); +} + +TEST_POINT("storage") { + test(); + non_constexpr_test(); + + static_assert(test()); +} + +} // namespace diff --git a/impl/any_view/test/view/bidirectional.cpp b/impl/any_view/test/view/bidirectional.cpp index 87ba444..4f7904f 100644 --- a/impl/any_view/test/view/bidirectional.cpp +++ b/impl/any_view/test/view/bidirectional.cpp @@ -140,7 +140,7 @@ constexpr bool test() { TEST_POINT("forward") { test(); - static_assert(test()); + // static_assert(test()); } } // namespace diff --git a/impl/any_view/test/view/contiguous.cpp b/impl/any_view/test/view/contiguous.cpp index 44d10d3..e8a8ff7 100644 --- a/impl/any_view/test/view/contiguous.cpp +++ b/impl/any_view/test/view/contiguous.cpp @@ -160,7 +160,7 @@ constexpr bool test() { TEST_POINT("forward") { test(); - static_assert(test()); + // static_assert(test()); } } // namespace diff --git a/impl/any_view/test/view/forward.cpp b/impl/any_view/test/view/forward.cpp index 109f4c2..3631d62 100644 --- a/impl/any_view/test/view/forward.cpp +++ b/impl/any_view/test/view/forward.cpp @@ -126,7 +126,7 @@ constexpr bool test() { TEST_POINT("forward") { test(); - static_assert(test()); + // static_assert(test()); } } // namespace diff --git a/impl/any_view/test/view/input.cpp b/impl/any_view/test/view/input.cpp index a07ed6a..3c355a9 100644 --- a/impl/any_view/test/view/input.cpp +++ b/impl/any_view/test/view/input.cpp @@ -111,7 +111,7 @@ constexpr bool test() { TEST_POINT("input") { test(); - static_assert(test()); + // static_assert(test()); } } // namespace diff --git a/impl/any_view/test/view/random_access.cpp b/impl/any_view/test/view/random_access.cpp index ddd22cc..841a142 100644 --- a/impl/any_view/test/view/random_access.cpp +++ b/impl/any_view/test/view/random_access.cpp @@ -159,7 +159,7 @@ TEST_POINT("forward") { auto st = v.end(); test(); - static_assert(test()); + // static_assert(test()); } } // namespace