diff --git a/examples/06_slices.cpp b/examples/06_slices.cpp index bea2eef..4228b66 100644 --- a/examples/06_slices.cpp +++ b/examples/06_slices.cpp @@ -45,7 +45,7 @@ void slices() // auto& oob = *ptr; // oob[1'000] = 0; - // Slice *iterators* are bounds checked by default as well. + // Slice *iterators* are bounds checked as well. // This means that trying to use an iterator which // would point to an invalid location will be a runtime error: // [[maybe_unused]] auto error1 = *slice.end(); diff --git a/examples/07_unchecked_slices.cpp b/examples/07_unchecked_slices.cpp new file mode 100644 index 0000000..1f70098 --- /dev/null +++ b/examples/07_unchecked_slices.cpp @@ -0,0 +1,63 @@ +// Copyright (c) 2025 Tristan Brindle (tcbrindle at gmail dot com) +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include + +#ifdef IMPORT_MODULE +import tcb.pointer; +#else +# include +#endif + +void unchecked_slices() +{ + std::vector vec{1, 2, 3, 4, 5}; + tcb::ptr ptr = tcb::ptr_to_mut(vec); + + auto& slice = *ptr; + + // As we saw in the last example, operations on slices are + // *bounds checked*, so for example asking for element 100 + // of a slice of 5 elements is a runtime error. Try + // uncommenting this line: + // [[maybe_unused]] auto crash = slice[100]; + + // Bounds checking can add some overhead, so we might want + // to avoid the checks in specific situations where we're + // sure we won't be going out of bounds. For this purpose, + // tcb::slice has a public member named `unchecked`. + // This provides the same operations as `slice`, but as + // the name suggests, omits bounds checks. + // + // For example, we can use it to get the first element of a + // slice without first checking whether the slice is empty: + auto front = slice.unchecked.front(); + + // We can use the subscript operator of an unchecked slice + // like so: + auto with_check = slice[1]; + auto without_check = slice.unchecked[1]; + + // Slice iterators are bounds checked, but if we want + // we can explicitly use unchecked iterators instead: + auto sum = std::accumulate(slice.unchecked.begin(), slice.unchecked.end(), 0); + + // We can call range algorithms on the unchecked slice as well + std::ranges::for_each(slice.unchecked, [](int) { + // Look ma, no bounds checks + }); + + // The default, bounds-checked slice should be what you use most of + // the time. But having `unchecked` available means you can explicitly + // omit bounds checks in specific places where they might cause unacceptable + // overhead, without sacrificing checks elsewhere in the program. + + [](auto&...) { }(vec, ptr, slice, front, with_check, without_check, sum); +} + +int main() { unchecked_slices(); } \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 55ee55e..b80f375 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -20,3 +20,4 @@ add_example(tcb.pointer.example_03 03_conversions.cpp) add_example(tcb.pointer.example_04 04_void_pointers.cpp) add_example(tcb.pointer.example_05 05_array_pointer_construction.cpp) add_example(tcb.pointer.example_06 06_slices.cpp) +add_example(tcb.pointer.example_07 07_unchecked_slices.cpp) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index ad6cafa..3ae3fab 100644 --- a/include/tcb/pointer.hpp +++ b/include/tcb/pointer.hpp @@ -42,7 +42,9 @@ DEALINGS IN THE SOFTWARE. # include // for std::strong_ordering # include # include +# include # include // for std::invoke +# include // for std::reverse_iterator # include // for std::addressof # include // for std::optional # include // for std::ranges::contiguous_range etc @@ -82,12 +84,14 @@ DEALINGS IN THE SOFTWARE. std::terminate(); \ } while (0) # else -# if defined(__has_builtin) +# if defined(_MSC_VER) +# define TCB_PTR_RUNTIME_ERROR(msg) __fastfail(7) // FAST_FAIL_FATAL_APP_EXIT +# elif defined(__has_builtin) # if __has_builtin(__builtin_trap) # define TCB_PTR_RUNTIME_ERROR(msg) __builtin_trap() +# else +# define TCB_PTR_RUNTIME_ERROR(msg) std::abort() # endif -# elif defined(_MSC_VER) -# define TCB_PTR_RUNTIME_ERROR(msg) __fastfail(7) // FAST_FAIL_FATAL_APP_EXIT # else # define TCB_PTR_RUNTIME_ERROR(msg) std::abort() # endif @@ -106,6 +110,18 @@ DEALINGS IN THE SOFTWARE. # define TCB_PTR_THROW(ex) TCB_PTR_RUNTIME_ERROR(ex.what()) #endif +#if !defined(TCB_PTR_OPTIONAL_MONADIC_SUPPORT) +# if __cpp_lib_optional >= 202110L +# define TCB_PTR_OPTIONAL_MONADIC_SUPPORT 1 +# endif +#endif + +#if !defined(TCB_PTR_OPTIONAL_RANGE_SUPPORT) +# if __cpp_lib_optional_range_support >= 202406L +# define TCB_PTR_OPTIONAL_RANGE_SUPPORT 1 +# endif +#endif + namespace tcb { // MARK: Object pointer @@ -154,8 +170,6 @@ struct TCB_PTR_GSL_POINTER(T) pointer { constexpr explicit operator T*() const noexcept { return addr_; } - constexpr explicit operator bool() const noexcept { return addr_ != nullptr; } - #ifdef __cpp_multidimensional_subscript constexpr auto operator[]() const noexcept -> T& { return *addr_; } #endif @@ -168,7 +182,7 @@ struct TCB_PTR_GSL_POINTER(T) pointer { friend constexpr auto operator==(pointer lhs, pointer rhs) -> bool { - return std::compare_three_way{}(lhs.addr_, rhs.addr_) == 0; + return std::ranges::equal_to{}(lhs.addr_, rhs.addr_); } friend constexpr auto operator<=>(pointer lhs, pointer rhs) -> std::strong_ordering @@ -279,9 +293,10 @@ struct TCB_PTR_GSL_POINTER(V) pointer : detail::void_pointer_base { return static_cast(this->addr_); } - explicit operator bool() const noexcept { return this->addr_ != nullptr; } - - friend auto operator==(pointer lhs, pointer rhs) -> bool { return lhs.addr_ == rhs.addr_; } + friend auto operator==(pointer lhs, pointer rhs) -> bool + { + return std::ranges::equal_to{}(lhs.addr_, rhs.addr_); + } friend auto operator<=>(pointer lhs, pointer rhs) -> std::strong_ordering { @@ -297,30 +312,42 @@ template struct TCB_PTR_GSL_POINTER(T) checked_iterator { private: T* start_ = nullptr; - std::ptrdiff_t pos_ = 0; - std::ptrdiff_t size_ = 0; + std::size_t pos_ = 0; + std::size_t sz_ = 0; friend struct checked_iterator>; + constexpr explicit checked_iterator(T* start, std::size_t pos, std::size_t size) + : start_(start), pos_(pos), sz_(size) + { + } + + struct buffer_t { + T* start_addr; + std::size_t size; + }; + public: using value_type = T; using reference = value_type&; using difference_type = std::ptrdiff_t; using iterator_category = std::contiguous_iterator_tag; - checked_iterator() = default; + static constexpr auto to_start_of(buffer_t buf) -> checked_iterator + { + return checked_iterator(buf.start_addr, 0, buf.size); + } - constexpr explicit checked_iterator(T* start, std::ptrdiff_t pos, std::ptrdiff_t size) - : start_(start), pos_(pos), size_(size) + static constexpr auto to_end_of(buffer_t buf) -> checked_iterator { - if (pos_ < 0 || pos_ > size_) { - TCB_PTR_RUNTIME_ERROR("Bad size or position in checked_iterator ctor"); - } + return checked_iterator(buf.start_addr, buf.size, buf.size); } + checked_iterator() = default; + constexpr checked_iterator(checked_iterator> const& other) requires(std::is_const_v) - : start_(other.start_), pos_(other.pos_), size_(other.size_) + : start_(other.start_), pos_(other.pos_), sz_(other.sz_) { } @@ -332,27 +359,30 @@ struct TCB_PTR_GSL_POINTER(T) checked_iterator { constexpr auto operator*() const -> reference { - if (pos_ == size_) { - TCB_PTR_RUNTIME_ERROR("Cannot dereference past-the-end iterator"); + if (pos_ >= sz_) { + TCB_PTR_RUNTIME_ERROR("Cannot dereference out-of-bounds iterator"); } return start_[pos_]; } constexpr auto operator[](difference_type idx) const -> reference { - if (idx >= (size_ - pos_) || idx < -pos_) { - TCB_PTR_RUNTIME_ERROR("Out of bounds random-access read"); + if ((pos_ + static_cast(idx)) >= sz_) { + TCB_PTR_RUNTIME_ERROR("Cannot dereference out-of-bounds iterator"); } - return start_[pos_ + idx]; + return start_[pos_ + static_cast(idx)]; } - constexpr auto operator->() const -> T* { return start_ + pos_; } + constexpr auto operator->() const -> T* + { + if (pos_ > sz_) { + TCB_PTR_RUNTIME_ERROR("Cannot form pointer from out-of-bounds iterator"); + } + return start_ + pos_; + } constexpr auto operator++() -> checked_iterator& { - if (pos_ == size_) { - TCB_PTR_RUNTIME_ERROR("Cannot increment past-the-end iterator"); - } ++pos_; return *this; } @@ -366,9 +396,6 @@ struct TCB_PTR_GSL_POINTER(T) checked_iterator { constexpr auto operator--() -> checked_iterator& { - if (pos_ == 0) { - TCB_PTR_RUNTIME_ERROR("Cannot decrement start iterator"); - } --pos_; return *this; } @@ -382,19 +409,13 @@ struct TCB_PTR_GSL_POINTER(T) checked_iterator { constexpr auto operator+=(difference_type offset) -> checked_iterator& { - if (offset > (size_ - pos_) || offset < -pos_) { - TCB_PTR_RUNTIME_ERROR("Out of bounds random-access jump"); - } - pos_ += offset; + pos_ += static_cast(offset); return *this; } constexpr auto operator-=(difference_type offset) -> checked_iterator& { - if (offset < (pos_ - size_) || offset > pos_) { - TCB_PTR_RUNTIME_ERROR("Out of bounds random-access jump"); - } - pos_ -= offset; + pos_ -= static_cast(offset); return *this; } @@ -416,7 +437,7 @@ struct TCB_PTR_GSL_POINTER(T) checked_iterator { friend constexpr auto operator-(checked_iterator const& lhs, checked_iterator const& rhs) -> difference_type { - return lhs.pos_ - rhs.pos_; + return static_cast(lhs.pos_) - static_cast(rhs.pos_); } friend auto operator==(checked_iterator const&, checked_iterator const&) -> bool = default; @@ -424,51 +445,119 @@ struct TCB_PTR_GSL_POINTER(T) checked_iterator { -> std::strong_ordering = default; }; -#ifndef TCB_PTR_USE_UNCHECKED_ITERATORS -template -using contiguous_iterator_t = checked_iterator; -#else -template -using contiguous_iterator_t = T*; -#endif - -template -constexpr auto make_begin_iterator(T* addr, std::size_t size [[maybe_unused]]) - -> contiguous_iterator_t -{ -#ifndef TCB_PTR_USE_UNCHECKED_ITERATORS - return checked_iterator(addr, 0, static_cast(size)); -#else - return addr; -#endif -} - -template -constexpr auto make_end_iterator(T* addr, std::size_t size) -> contiguous_iterator_t -{ -#ifndef TCB_PTR_USE_UNCHECKED_ITERATORS - return checked_iterator(addr, static_cast(size), - static_cast(size)); -#else - return addr + size; -#endif -} - } // namespace detail -// MARK: Slice +// MARK: Unchecked slice + +TCB_PTR_EXPORT template +struct TCB_PTR_GSL_POINTER(T) slice; TCB_PTR_EXPORT template - requires(std::is_object_v && !std::is_const_v) -struct TCB_PTR_GSL_POINTER(T) slice { +struct TCB_PTR_GSL_POINTER(T) unchecked_slice { private: + static_assert(std::is_object_v && !std::is_const_v, + "slice element must be a non-const object type"); + T* addr_; std::size_t sz_; + friend struct slice; + + constexpr explicit unchecked_slice(T* addr, std::size_t sz) : addr_(addr), sz_(sz) { } + + unchecked_slice(unchecked_slice const&) = default; + auto operator=(unchecked_slice const&) -> unchecked_slice& = default; + +public: + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = T&; + using const_reference = T const&; + using pointer = value_type*; + using const_pointer = value_type const*; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + constexpr auto operator[](size_type idx) -> reference { return addr_[idx]; } + + constexpr auto operator[](size_type idx) const -> const_reference { return addr_[idx]; } + + constexpr auto front() -> reference { return addr_[0]; } + constexpr auto front() const -> const_reference { return addr_[0]; } + + constexpr auto back() -> reference { return addr_[sz_ - 1]; } + constexpr auto back() const -> const_reference { return addr_[sz_ - 1]; } + + constexpr auto size() const -> size_type { return sz_; } + constexpr auto empty() const -> bool { return sz_ == 0; } + + constexpr auto data() -> pointer { return addr_; } + constexpr auto data() const -> const_pointer { return addr_; } + + constexpr auto begin() -> iterator { return addr_; } + constexpr auto begin() const -> const_iterator { return addr_; } + constexpr auto cbegin() const -> const_iterator { return begin(); } + + constexpr auto end() -> iterator { return addr_ + sz_; } + constexpr auto end() const -> const_iterator { return addr_ + sz_; } + constexpr auto cend() const -> const_iterator { return end(); } + + constexpr auto rbegin() -> reverse_iterator { return reverse_iterator(end()); } + constexpr auto rbegin() const -> const_reverse_iterator + { + return const_reverse_iterator(end()); + } + constexpr auto crbegin() const -> const_reverse_iterator { return rbegin(); } + + constexpr auto rend() -> reverse_iterator { return reverse_iterator(begin()); } + constexpr auto rend() const -> const_reverse_iterator + { + return const_reverse_iterator(begin()); + } + constexpr auto crend() const -> const_reverse_iterator { return rend(); } + + friend constexpr auto operator==(unchecked_slice const& lhs, unchecked_slice const& rhs) -> bool + requires(std::equality_comparable && !std::is_array_v) + { + return std::ranges::equal(lhs, rhs); + } + + friend constexpr auto operator<=>(unchecked_slice const& lhs, unchecked_slice const& rhs) + requires(std::totally_ordered && !std::is_array_v) + { + auto cmp = [](const_reference lhs, const_reference rhs) { + if constexpr (std::three_way_comparable) { + return lhs <=> rhs; + } else { + if (lhs == rhs) { + return std::weak_ordering::equivalent; + } else if (lhs < rhs) { + return std::weak_ordering::less; + } else { + return std::weak_ordering::greater; + } + } + }; + return std::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), + rhs.end(), cmp); + } +}; + +// MARK: Slice + +template +struct slice { + + unchecked_slice unchecked; + +private: friend struct pointer; friend struct pointer; - constexpr explicit slice(T* addr, std::size_t sz) : addr_(addr), sz_(sz) { } + constexpr explicit slice(T* addr, std::size_t sz) : unchecked(addr, sz) { } slice(slice const&) = default; auto operator=(slice const&) -> slice& = default; @@ -481,90 +570,99 @@ struct TCB_PTR_GSL_POINTER(T) slice { using const_reference = T const&; using pointer = value_type*; using const_pointer = value_type const*; - using iterator = detail::contiguous_iterator_t; - using const_iterator = detail::contiguous_iterator_t; + using iterator = detail::checked_iterator; + using const_iterator = detail::checked_iterator; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; constexpr auto operator[](size_type idx) -> reference { - if (idx >= sz_) { + if (idx >= unchecked.sz_) { TCB_PTR_RUNTIME_ERROR("Index out of bounds in slice access"); } - return addr_[idx]; + return unchecked.addr_[idx]; } constexpr auto operator[](size_type idx) const -> const_reference { - if (idx >= sz_) { + if (idx >= unchecked.sz_) { TCB_PTR_RUNTIME_ERROR("Index out of bounds in slice access"); } - return addr_[idx]; + return unchecked.addr_[idx]; } constexpr auto at(size_type idx) -> reference { - if (idx >= sz_) { + if (idx >= unchecked.sz_) { TCB_PTR_THROW(std::out_of_range("Index out of bounds in slice access")); } - return addr_[idx]; + return unchecked.addr_[idx]; } constexpr auto at(size_type idx) const -> const_reference { - if (idx >= sz_) { + if (idx >= unchecked.sz_) { TCB_PTR_THROW(std::out_of_range("Index out of bounds in slice access")); } - return addr_[idx]; + return unchecked.addr_[idx]; } constexpr auto front() -> reference { - if (sz_ == 0) { + if (unchecked.sz_ == 0) { TCB_PTR_RUNTIME_ERROR("Accessing front of empty slice"); } - return addr_[0]; + return unchecked.addr_[0]; } constexpr auto front() const -> const_reference { - if (sz_ == 0) { + if (unchecked.sz_ == 0) { TCB_PTR_RUNTIME_ERROR("Accessing front of empty slice"); } - return addr_[0]; + return unchecked.addr_[0]; } constexpr auto back() -> reference { - if (sz_ == 0) { + if (unchecked.sz_ == 0) { TCB_PTR_RUNTIME_ERROR("Accessing back of empty slice"); } - return addr_[sz_ - 1]; + return unchecked.addr_[unchecked.sz_ - 1]; } constexpr auto back() const -> const_reference { - if (sz_ == 0) { + if (unchecked.sz_ == 0) { TCB_PTR_RUNTIME_ERROR("Accessing back of empty slice"); } - return addr_[sz_ - 1]; + return unchecked.addr_[unchecked.sz_ - 1]; } - constexpr auto size() const -> size_type { return sz_; } - constexpr auto empty() const -> bool { return sz_ == 0; } + constexpr auto size() const -> size_type { return unchecked.sz_; } + constexpr auto empty() const -> bool { return unchecked.sz_ == 0; } - constexpr auto data() -> pointer { return addr_; } - constexpr auto data() const -> const_pointer { return addr_; } + constexpr auto data() -> pointer { return unchecked.addr_; } + constexpr auto data() const -> const_pointer { return unchecked.addr_; } - constexpr auto begin() -> iterator { return detail::make_begin_iterator(addr_, sz_); } + constexpr auto begin() -> iterator + { + return iterator::to_start_of({.start_addr = data(), .size = size()}); + } constexpr auto begin() const -> const_iterator { - return detail::make_begin_iterator(addr_, sz_); + return const_iterator::to_start_of({.start_addr = data(), .size = size()}); } constexpr auto cbegin() const -> const_iterator { return begin(); } - constexpr auto end() -> iterator { return detail::make_end_iterator(addr_, sz_); } - constexpr auto end() const -> const_iterator { return detail::make_end_iterator(addr_, sz_); } + constexpr auto end() -> iterator + { + return iterator::to_end_of({.start_addr = data(), .size = size()}); + } + constexpr auto end() const -> const_iterator + { + return const_iterator::to_end_of({.start_addr = data(), .size = size()}); + } constexpr auto cend() const -> const_iterator { return end(); } constexpr auto rbegin() -> reverse_iterator { return reverse_iterator(end()); } @@ -582,30 +680,12 @@ struct TCB_PTR_GSL_POINTER(T) slice { constexpr auto crend() const -> const_reverse_iterator { return rend(); } friend constexpr auto operator==(slice const& lhs, slice const& rhs) -> bool - requires std::equality_comparable - { - return std::ranges::equal(lhs, rhs); - } + requires(std::equality_comparable && !std::is_array_v) + = default; friend constexpr auto operator<=>(slice const& lhs, slice const& rhs) - requires std::totally_ordered - { - auto cmp = [](const_reference lhs, const_reference rhs) { - if constexpr (std::three_way_comparable) { - return lhs <=> rhs; - } else { - if (lhs < rhs) { - return std::weak_ordering::less; - } else if (rhs < lhs) { - return std::weak_ordering::greater; - } else { - return std::weak_ordering::equivalent; - } - } - }; - return std::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), - rhs.end(), cmp); - } + requires(std::totally_ordered && !std::is_array_v) + = default; }; // MARK: Array pointer @@ -625,11 +705,6 @@ struct TCB_PTR_GSL_POINTER(T) pointer { using slice_type = slice>; mutable slice_type slice_ = slice_type(nullptr, 0); - friend class std::optional>; - - // Secret nullptr constructor for use by optional - constexpr pointer(std::nullptr_t) noexcept { } - constexpr explicit pointer(T* ptr, std::size_t sz) : slice_(const_cast*>(ptr), sz) { @@ -651,12 +726,15 @@ struct TCB_PTR_GSL_POINTER(T) pointer { static constexpr auto from_address_with_size(U* ptr TCB_PTR_LIFETIME_BOUND, std::size_t sz) -> pointer { - if (ptr == nullptr) { - TCB_PTR_RUNTIME_ERROR("Null pointer passed to from_address_with_size()"); + if (ptr == nullptr && sz > 0) { + TCB_PTR_RUNTIME_ERROR( + "Null pointer and nonzero size passed to from_address_with_size()"); } return pointer(ptr, sz); } + pointer() = default; + pointer(pointer const&) = default; // If we are const, allow copy-construction from non-const @@ -688,8 +766,6 @@ struct TCB_PTR_GSL_POINTER(T) pointer { } void operator->() const&& = delete; - constexpr explicit operator bool() const noexcept { return slice_.addr_ != nullptr; } - friend constexpr auto operator==(pointer const& lhs, pointer const& rhs) -> bool { return lhs->data() == rhs->data() && lhs->size() == rhs->size(); @@ -846,6 +922,9 @@ TCB_PTR_EXPORT inline constexpr auto& ptr_to_mut_array = pointer_to_mut_array; } // namespace tcb +template +constexpr bool std::ranges::enable_borrowed_range> = true; + template constexpr bool std::ranges::enable_borrowed_range> = true; @@ -870,17 +949,35 @@ struct hash> { } }; +} // namespace std + +namespace tcb::detail { + +template +inline constexpr bool is_optional_v = false; + +template +inline constexpr bool is_optional_v> = true; + +} // namespace tcb::detail + +namespace std { + // MARK: std::optional template + requires(!std::is_unbounded_array_v) class optional> { private: tcb::pointer ptr_; public: using value_type = tcb::pointer; - using iterator = tcb::detail::contiguous_iterator_t; - using const_iterator = tcb::detail::contiguous_iterator_t; + +#ifdef TCB_PTR_OPTIONAL_RANGE_SUPPORT + using iterator = tcb::detail::checked_iterator; + using const_iterator = tcb::detail::checked_iterator; +#endif /* * Constructors @@ -1037,25 +1134,31 @@ class optional> { /* * Iterator support */ +#ifdef TCB_PTR_OPTIONAL_RANGE_SUPPORT constexpr auto begin() noexcept -> iterator { - return tcb::detail::make_begin_iterator(std::addressof(ptr_), has_value() ? 1 : 0); + return iterator::to_start_of( + {.start_addr = std::addressof(ptr_), .size = has_value() ? 1u : 0u}); } constexpr auto begin() const noexcept -> const_iterator { - return tcb::detail::make_begin_iterator(std::addressof(ptr_), has_value() ? 1 : 0); + return const_iterator::to_start_of( + {.start_addr = std::addressof(ptr_), .size = has_value() ? 1u : 0u}); } constexpr auto end() noexcept -> iterator { - return tcb::detail::make_end_iterator(std::addressof(ptr_), has_value() ? 1 : 0); + return iterator::to_end_of( + {.start_addr = std::addressof(ptr_), .size = has_value() ? 1u : 0u}); } constexpr auto end() const noexcept -> const_iterator { - return tcb::detail::make_end_iterator(std::addressof(ptr_), has_value() ? 1 : 0); + return const_iterator::to_end_of( + {.start_addr = std::addressof(ptr_), .size = has_value() ? 1u : 0u}); } +#endif /* * Observers @@ -1108,7 +1211,7 @@ class optional> { return std::move(ptr_); } - constexpr auto has_value() const noexcept -> bool { return static_cast(ptr_); } + constexpr auto has_value() const noexcept -> bool { return ptr_.to_address() != nullptr; } constexpr explicit operator bool() const noexcept { return has_value(); } constexpr auto value() & -> tcb::pointer& @@ -1144,9 +1247,10 @@ class optional> { } template > - requires std::is_convertible_v> constexpr auto value_or(U&& default_value) const& -> tcb::pointer { + static_assert(std::is_convertible_v>); + if (has_value()) { return ptr_; } else { @@ -1155,9 +1259,10 @@ class optional> { } template > - requires std::is_convertible_v> constexpr auto value_or(U&& default_value) && -> tcb::pointer { + static_assert(std::is_convertible_v>); + if (has_value()) { return std::move(ptr_); } else { @@ -1168,36 +1273,46 @@ class optional> { /* * Monadic operations */ +#ifdef TCB_PTR_OPTIONAL_MONADIC_SUPPORT template - requires invocable&> constexpr auto and_then(F&& f) & { + static_assert(invocable&>); + using R = invoke_result_t&>; + static_assert(tcb::detail::is_optional_v); + if (has_value()) { - return std::invoke(static_cast(f), ptr_); + return std::invoke(static_cast(f), value()); } else { - return remove_cvref_t&>>{}; + return remove_cvref_t{}; } } template - requires invocable const&> constexpr auto and_then(F&& f) const& { + static_assert(invocable const&>); + using R = invoke_result_t const&>; + static_assert(tcb::detail::is_optional_v); + if (has_value()) { - return std::invoke(static_cast(f), ptr_); + return std::invoke(static_cast(f), value()); } else { - return remove_cvref_t const&>>{}; + return remove_cvref_t{}; } } template - requires invocable&&> constexpr auto and_then(F&& f) && { + static_assert(invocable&&>); + using R = invoke_result_t&&>; + static_assert(tcb::detail::is_optional_v); + if (has_value()) { - return std::invoke(static_cast(f), std::move(ptr_)); + return std::invoke(static_cast(f), std::move(value())); } else { - return remove_cvref_t&&>>{}; + return remove_cvref_t{}; } } @@ -1205,61 +1320,87 @@ class optional> { requires invocable const&&> constexpr auto and_then(F&& f) const&& { + static_assert(invocable const&&>); + using R = invoke_result_t const&&>; + static_assert(tcb::detail::is_optional_v); + if (has_value()) { - return std::invoke(static_cast(f), std::move(ptr_)); + return std::invoke(static_cast(f), std::move(value())); } else { - return remove_cvref_t const&&>>{}; + return remove_cvref_t{}; } } - template &>>> - requires(!same_as && !same_as) - constexpr auto transform(F&& f) & -> optional + template + constexpr auto transform(F&& f) & { + static_assert(invocable&>); + using U = remove_cvref_t&>>; + static_assert(is_object_v && !is_array_v); + static_assert(!is_same_v); + static_assert(!is_same_v); + if (has_value()) { - return optional(std::invoke(static_cast(f), ptr_)); + return optional(in_place, std::invoke(static_cast(f), **this)); } else { return optional{}; } } - template const&>>> - requires(!same_as && !same_as) - constexpr auto transform(F&& f) const& -> optional + template + constexpr auto transform(F&& f) const& { + static_assert(invocable const&>); + using U = remove_cvref_t const&>>; + static_assert(is_object_v && !is_array_v); + static_assert(!is_same_v); + static_assert(!is_same_v); + if (has_value()) { - return optional(std::invoke(static_cast(f), ptr_)); + return optional(in_place, std::invoke(static_cast(f), **this)); } else { return optional{}; } } - template &&>>> - requires(!same_as && !same_as) - constexpr auto transform(F&& f) && -> optional + template + constexpr auto transform(F&& f) && { + static_assert(invocable>); + using U = remove_cvref_t>>; + static_assert(is_object_v && !is_array_v); + static_assert(!is_same_v); + static_assert(!is_same_v); + if (has_value()) { - return optional(std::invoke(static_cast(f), std::move(ptr_))); + return optional(in_place, std::invoke(static_cast(f), std::move(**this))); } else { return optional{}; } } - template const&&>>> - requires(!same_as && !same_as) - constexpr auto transform(F&& f) const&& -> optional + template + constexpr auto transform(F&& f) const&& { + static_assert(invocable const>); + using U = remove_cvref_t const>>; + static_assert(is_object_v && !is_array_v); + static_assert(!is_same_v); + static_assert(!is_same_v); + if (has_value()) { - return optional(std::invoke(static_cast(f), std::move(ptr_))); + return optional(in_place, std::invoke(static_cast(f), std::move(**this))); } else { return optional{}; } } template - requires invocable && same_as, optional> + requires invocable constexpr auto or_else(F&& f) const& -> optional { + static_assert(is_same_v, optional>); + if (has_value()) { return *this; } else { @@ -1268,15 +1409,18 @@ class optional> { } template - requires invocable && same_as, optional> + requires invocable constexpr auto or_else(F&& f) && -> optional { + static_assert(is_same_v, optional>); + if (has_value()) { return std::move(*this); } else { return std::invoke(static_cast(f)); } } +#endif // TCB_PTR_OPTIONAL_MONADIC_SUPPORT /* * Modifiers diff --git a/tests/pointer.config.hpp b/tests/pointer.config.hpp index b777493..cb5bf60 100644 --- a/tests/pointer.config.hpp +++ b/tests/pointer.config.hpp @@ -10,3 +10,6 @@ struct ptr_runtime_error : std::runtime_error { using std::runtime_error::runtime_error; }; #define TCB_PTR_RUNTIME_ERROR(msg) throw ptr_runtime_error(msg) + +#define TCB_PTR_OPTIONAL_MONADIC_SUPPORT 1 +#define TCB_PTR_OPTIONAL_RANGE_SUPPORT 1 diff --git a/tests/pointer.test.cpp b/tests/pointer.test.cpp index c0bebc0..372a839 100755 --- a/tests/pointer.test.cpp +++ b/tests/pointer.test.cpp @@ -119,14 +119,21 @@ constexpr bool test_pointer_static_properties() // I know arrays of unknown bound are technically objects, but... constexpr bool is_object = std::is_object_v && !std::is_unbounded_array_v; + constexpr bool is_array = std::is_unbounded_array_v; // pointer to object is the same size as T* if constexpr (is_object) { static_assert(sizeof(P) == sizeof(T*)); } - static_assert(not std::is_default_constructible_v

); - static_assert(not std::default_initializable

); + // array pointers are default constructible, but object pointers are not + if constexpr (is_array) { + static_assert(std::is_default_constructible_v

); + static_assert(std::default_initializable

); + } else { + static_assert(not std::is_default_constructible_v

); + static_assert(not std::default_initializable

); + } // pointer is copyable, movable, etc (type traits) static_assert(std::is_copy_constructible_v

); @@ -162,13 +169,6 @@ constexpr bool test_pointer_static_properties() static_assert(std::totally_ordered

); static_assert(std::three_way_comparable); - // pointer is explicitly (but not implicitly) convertible to bool - static_assert(not std::is_convertible_v); - static_assert(requires(P& p) { - { static_cast(p) }; - { p ? 1 : 0 }; - }); - // pointer_to object is explicitly but not implicitly convertible to T* static_assert(not std::is_convertible_v); if constexpr (is_object) { @@ -178,7 +178,7 @@ constexpr bool test_pointer_static_properties() } // P::to_address() returns the correct type for non-arrays - if constexpr (!std::is_unbounded_array_v) { + if constexpr (!is_array) { static_assert(std::same_as().to_address()), T*>); } @@ -343,9 +343,6 @@ constexpr bool test_pointer_to_object() // explicit cast to int* works correctly REQUIRE(static_cast(p) == std::addressof(i)); - // bool contextual conversion works as expected - REQUIRE(p); - // dereferencing works correctly REQUIRE(*p == 0); *p = 1; @@ -597,8 +594,8 @@ constexpr bool test_checked_iterator() { std::array arr{1, 2, 3, 4, 5}; - auto start = Iter(arr.data(), 0, arr.size()); - auto end = Iter(arr.data(), arr.size(), arr.size()); + auto start = Iter::to_start_of({arr.data(), arr.size()}); + auto end = Iter::to_end_of({arr.data(), arr.size()}); REQUIRE(std::ranges::equal(arr, std::ranges::subrange(start, end))); REQUIRE(std::ranges::equal(arr | std::views::reverse, @@ -609,7 +606,7 @@ constexpr bool test_checked_iterator() { std::array arr{1, 2, 3, 4, 5}; - auto start = Iter(arr.data(), 0, arr.size()); + auto start = Iter::to_start_of({arr.data(), arr.size()}); auto next = std::next(start); REQUIRE(start == start); @@ -624,8 +621,8 @@ constexpr bool test_checked_iterator() { std::array arr{1, 2, 3, 4, 5}; - auto start = Iter(arr.data(), 0, arr.size()); - auto end = Iter(arr.data(), arr.size(), arr.size()); + auto start = Iter::to_start_of({arr.data(), arr.size()}); + auto end = Iter::to_end_of({arr.data(), arr.size()}); REQUIRE(start + 5 == end); REQUIRE(end - 5 == start); @@ -636,7 +633,7 @@ constexpr bool test_checked_iterator() { std::array arr{1, 2, 3, 4, 5}; - Iter start = Iter(arr.data(), 0, arr.size()); + Iter start = Iter::to_start_of({arr.data(), arr.size()}); ++start; CIter copy = start; @@ -655,26 +652,26 @@ bool test_checked_iterator_bounds_checking() std::array arr{1, 2, 3, 4, 5}; - auto start = Iter(arr.data(), 0, arr.size()); - auto end = Iter(arr.data(), arr.size(), arr.size()); + auto start = Iter::to_start_of({arr.data(), arr.size()}); + auto end = Iter::to_end_of({arr.data(), arr.size()}); // Cannot deref end iterator REQUIRE_ERROR(*end); - // Cannot advance end iterator - REQUIRE_ERROR(++Iter(end)); - REQUIRE_ERROR(Iter(end)++); + // Cannot deref advanced end iterator + REQUIRE_ERROR(*++Iter(end)); + REQUIRE_ERROR(*Iter(end)++); - // Cannot decrement start iterator - REQUIRE_ERROR(--Iter(start)); - REQUIRE_ERROR(Iter(start)--); + // Cannot deref decremented start iterator + REQUIRE_ERROR(*--Iter(start)); + // REQUIRE_ERROR(*Iter(start)--); - // Cannot perform out-of-bounds RA jumps - REQUIRE_ERROR((start + -1)); - REQUIRE_ERROR((start - 1)); - REQUIRE_ERROR((start + std::ssize(arr) + 1)); - REQUIRE_ERROR((end + 1)); - REQUIRE_ERROR((end - std::ssize(arr) - 1)); + // Cannot deref after out-of-bounds RA jumps + REQUIRE_ERROR(*(start + -1)); + REQUIRE_ERROR(*(start - 1)); + REQUIRE_ERROR(*(start + std::ssize(arr) + 1)); + REQUIRE_ERROR(*(end + 1)); + REQUIRE_ERROR(*(end - std::ssize(arr) - 1)); REQUIRE_ERROR(start[-1]); REQUIRE_ERROR(start[std::ssize(arr)]); @@ -683,14 +680,14 @@ bool test_checked_iterator_bounds_checking() REQUIRE_ERROR(end[-std::ssize(arr) - 1]); // Integer overflow checks - REQUIRE_ERROR((start + PTRDIFF_MAX)); - REQUIRE_ERROR((start + PTRDIFF_MIN)); - REQUIRE_ERROR((start - PTRDIFF_MAX)); - REQUIRE_ERROR((start - PTRDIFF_MIN)); - REQUIRE_ERROR((end + PTRDIFF_MAX)); - REQUIRE_ERROR((end + PTRDIFF_MIN)); - REQUIRE_ERROR((end - PTRDIFF_MAX)); - REQUIRE_ERROR((end - PTRDIFF_MIN)); + REQUIRE_ERROR(*(start + PTRDIFF_MAX)); + REQUIRE_ERROR(*(start + PTRDIFF_MIN)); + REQUIRE_ERROR(*(start - PTRDIFF_MAX)); + REQUIRE_ERROR(*(start - PTRDIFF_MIN)); + REQUIRE_ERROR(*(end + PTRDIFF_MAX)); + REQUIRE_ERROR(*(end + PTRDIFF_MIN)); + REQUIRE_ERROR(*(end - PTRDIFF_MAX)); + REQUIRE_ERROR(*(end - PTRDIFF_MIN)); REQUIRE_ERROR(start[PTRDIFF_MAX]); REQUIRE_ERROR(start[PTRDIFF_MIN]); REQUIRE_ERROR(end[PTRDIFF_MAX]); @@ -703,10 +700,9 @@ bool test_checked_iterator_bounds_checking() * MARK: Slice tests */ +template constexpr bool test_slice_traits() { - using S = tcb::slice; - // Slices are not default constructible, copyable or movable static_assert(not std::is_default_constructible_v); static_assert(not std::is_copy_constructible_v); @@ -748,7 +744,8 @@ constexpr bool test_slice_traits() return true; } -static_assert(test_slice_traits()); +static_assert(test_slice_traits>()); +static_assert(test_slice_traits>()); struct no_spaceship { int i; @@ -760,6 +757,16 @@ struct no_spaceship { constexpr bool operator>=(no_spaceship other) const { return !(*this < other); } }; +struct equality_only { + bool operator==(equality_only const&) const = default; +}; + +struct spaceship_only { + int i; + + friend constexpr auto operator<=>(spaceship_only a, spaceship_only b) { return a.i <=> b.i; } +}; + constexpr bool test_slice() { // Basic slice functionality @@ -857,6 +864,21 @@ constexpr bool test_slice() static_assert(std::same_as); REQUIRE(cmp == std::weak_ordering::equivalent); } + + // Comparison operators are constrained as expected + { + using incomparable = std::span; + + static_assert(std::equality_comparable>); + static_assert(std::equality_comparable>); + static_assert(not std::equality_comparable>); + static_assert(not std::equality_comparable>); + + static_assert(std::three_way_comparable>); + static_assert(not std::three_way_comparable>); + static_assert(not std::three_way_comparable>); + static_assert(not std::three_way_comparable>); + } } // Bounds checking works correctly @@ -891,6 +913,127 @@ constexpr bool test_slice() } static_assert(test_slice()); +constexpr bool test_unchecked_slice() +{ + // Basic slice functionality + { + std::array arr{0, 1, 2, 3, 4}; + + auto ptr = tcb::ptr::pointer_to(arr); + auto& slice = ptr->unchecked; + + REQUIRE(&slice[0] == &arr[0]); + REQUIRE(&slice.front() == &arr.front()); + REQUIRE(&slice.back() == &arr.back()); + + REQUIRE(slice.size() == arr.size()); + REQUIRE(slice.empty() == arr.empty()); + REQUIRE(slice.data() == arr.data()); + + REQUIRE(std::ranges::equal(slice, arr)); + REQUIRE(std::ranges::equal(slice.cbegin(), slice.cend(), arr.cbegin(), arr.cend())); + REQUIRE(std::ranges::equal(slice | std::views::reverse, arr | std::views::reverse)); + REQUIRE(std::ranges::equal(slice.crbegin(), slice.crend(), arr.crbegin(), arr.crend())); + } + + // Same again, but const this time + { + std::array const arr{0, 1, 2, 3, 4}; + + auto ptr = tcb::ptr::pointer_to(arr); + auto& slice = ptr->unchecked; + + REQUIRE(&slice[0] == &arr[0]); + REQUIRE(&slice.front() == &arr.front()); + REQUIRE(&slice.back() == &arr.back()); + + REQUIRE(slice.size() == arr.size()); + REQUIRE(slice.empty() == arr.empty()); + REQUIRE(slice.data() == arr.data()); + + REQUIRE(std::ranges::equal(slice, arr)); + REQUIRE(std::ranges::equal(slice.cbegin(), slice.cend(), arr.cbegin(), arr.cend())); + REQUIRE(std::ranges::equal(slice | std::views::reverse, arr | std::views::reverse)); + REQUIRE(std::ranges::equal(slice.crbegin(), slice.crend(), arr.crbegin(), arr.crend())); + } + + // Empty ranges are handled correctly + { + std::array arr{}; + auto ptr = tcb::ptr::pointer_to(arr); + auto& slice = ptr->unchecked; + + REQUIRE(slice.size() == 0); + REQUIRE(slice.empty()); + REQUIRE(slice.data() == arr.data()); + + REQUIRE(std::ranges::equal(slice, arr)); + } + + // Slice comparisons work as expected + { + auto array = std::array{1, 2, 3, 4, 5}; + auto same_array = array; + auto shorter_array = std::array{1, 2, 3, 4}; + auto different_array = std::array{1, 2, 99, 4, 5}; + + auto p_array = tcb::ptr::pointer_to(array); + auto p_same_array = tcb::ptr::pointer_to(same_array); + auto p_shorter_array = tcb::ptr::pointer_to(shorter_array); + auto p_different_array = tcb::ptr::pointer_to(different_array); + + auto& s_array = p_array->unchecked; + auto& s_same_array = p_same_array->unchecked; + auto& s_shorter_array = p_shorter_array->unchecked; + auto& s_different_array = p_different_array->unchecked; + + REQUIRE(s_array == s_same_array); + REQUIRE(s_array != s_shorter_array); + REQUIRE(s_array != s_different_array); + + REQUIRE(s_array <=> s_same_array == std::strong_ordering::equal); + REQUIRE(s_array <=> s_shorter_array == std::strong_ordering::greater); + REQUIRE(s_shorter_array <=> s_array == std::strong_ordering::less); + + // Float comparison should be partially ordered, and handle nans + if (!(compiler_is_msvc && std::is_constant_evaluated())) { + float nan = std::numeric_limits::quiet_NaN(); + float floats[] = {1.0f, nan, 3.0f}; + auto p_floats = tcb::ptr::pointer_to(floats); + auto float_cmp = p_floats->unchecked <=> p_floats->unchecked; + static_assert(std::same_as); + REQUIRE(float_cmp == std::partial_ordering::unordered); + } + + // We can compare types without a spaceship operator + { + no_spaceship ns[] = {{1}, {2}, {3}}; + auto ptr = tcb::ptr_to_array(ns); + auto cmp = ptr->unchecked <=> ptr->unchecked; + static_assert(std::same_as); + REQUIRE(cmp == std::weak_ordering::equivalent); + } + + // Comparison operators are constrained as expected + { + using incomparable = std::span; + + static_assert(std::equality_comparable>); + static_assert(std::equality_comparable>); + static_assert(not std::equality_comparable>); + static_assert(not std::equality_comparable>); + + static_assert(std::three_way_comparable>); + static_assert(not std::three_way_comparable>); + static_assert(not std::three_way_comparable>); + static_assert(not std::three_way_comparable>); + } + } + + return true; +} +static_assert(test_unchecked_slice()); + /* * MARK: array ptr tests */ @@ -1038,11 +1181,15 @@ constexpr bool test_array_pointer() REQUIRE(ptr2->data() == &val && ptr2->size() == 1); REQUIRE(ptr2->at(0) == 99); - // Can create an array of size zero + // Can create an array of size zero with non-null data address auto ptr3 = pointer::from_address_with_size(array, 0); REQUIRE(ptr3->data() == array && ptr3->size() == 0); - // Passing a null pointer is a runtime error + // Can create a null array of size zero + auto ptr4 = pointer::from_address_with_size((int*)nullptr, 0); + REQUIRE(ptr4->data() == nullptr && ptr4->size() == 0); + + // Passing a null pointer with nonzero size is a runtime error if (!std::is_constant_evaluated()) { REQUIRE_ERROR(pointer::from_address_with_size((int*)nullptr, 1)); } @@ -1065,7 +1212,11 @@ constexpr bool test_array_pointer() auto ptr3 = pointer::from_address_with_size(array, 0); REQUIRE(ptr3->data() == array && ptr3->size() == 0); - // Passing a null pointer is a runtime error + // Can create a null array of size zero + auto ptr4 = pointer::from_address_with_size((int const*)nullptr, 0); + REQUIRE(ptr4->data() == nullptr && ptr4->size() == 0); + + // Passing a null pointer with nonzero size is a runtime error if (!std::is_constant_evaluated()) { REQUIRE_ERROR(pointer::from_address_with_size((int const*)nullptr, 1)); } @@ -1076,6 +1227,9 @@ constexpr bool test_array_pointer() std::array arr1{1, 2, 3, 4, 5}; std::array arr2{6, 7, 8, 9, 10}; + auto p0 = pointer(); + REQUIRE(p0->data() == nullptr && p0->size() == 0); + auto p1 = ptr_to_mut_array(arr1); auto p2 = p1; // copy-construct REQUIRE(p2->data() == arr1.data() && p2->size() == arr1.size()); @@ -1592,6 +1746,32 @@ constexpr bool test_std_optional_specialisation() REQUIRE(o1->to_address() == &j); } + // value_or + { + using Opt = std::optional>; + + int value = 99; + int default_value = 42; + + // lvalue + Opt opt = std::nullopt; + auto result = opt.value_or(ptr_to_mut(default_value)); + REQUIRE(result.to_address() == &default_value); + + opt = pointer_to_mut(value); + result = opt.value_or(pointer_to_mut(default_value)); + REQUIRE(result.to_address() == &value); + + // rvalue + opt.reset(); + result = std::move(opt).value_or(ptr_to_mut(default_value)); + REQUIRE(result.to_address() == &default_value); + + opt = pointer_to_mut(value); + result = std::move(opt).value_or(pointer_to_mut(default_value)); + REQUIRE(result.to_address() == &value); + } + // Iterator support { using Opt = std::optional>; @@ -1625,10 +1805,9 @@ constexpr bool test_std_optional_specialisation() REQUIRE(i == 1000); } - // optional> works correctly + // optional> specialisation is *not* used { using Opt = std::optional>; - static_assert(sizeof(Opt) == sizeof(tcb::pointer)); Opt opt{}; REQUIRE(not opt.has_value()); @@ -1643,6 +1822,148 @@ constexpr bool test_std_optional_specialisation() std::ranges::fill(**opt, 99); REQUIRE(std::ranges::all_of(arr, [](int i) { return i == 99; })); + + // Can differentiate between a disenaged optional and one holding + // a pointer to an empty array + opt = tcb::array_ptr(); + + REQUIRE(opt.has_value()); + REQUIRE((**opt).data() == nullptr); + REQUIRE((**opt).size() == 0); + + opt.reset(); + REQUIRE(not opt.has_value()); + } + + // Monadic operations + { + using Opt = std::optional>; + + int value = 42; + Opt engaged = tcb::pointer_to_mut(value); + Opt disengaged = std::nullopt; + + // and_then: engaged and disengaged cases + { + bool called = false; + auto func = [&](auto& p) -> std::optional { + called = true; + static_assert(std::same_as&>); + REQUIRE(p.to_address() == &value); + return 99; + }; + + auto result = engaged.and_then(func); + + static_assert(std::same_as>); + REQUIRE(called); + REQUIRE(result.has_value()); + REQUIRE(*result == 99); + + called = false; + auto empty_result = disengaged.and_then(func); + + static_assert(std::same_as>); + REQUIRE(not called); + REQUIRE(not empty_result.has_value()); + } + + // and_then: const lvalue, rvalue, and const rvalue overloads + { + auto from_const_lvalue + = std::as_const(engaged).and_then([](auto&& p) -> std::optional { + static_assert(std::same_as const&>); + return 99; + }); + REQUIRE(from_const_lvalue.has_value()); + REQUIRE(*from_const_lvalue == 99); + + auto from_rvalue + = Opt(tcb::pointer_to_mut(value)).and_then([](auto&& p) -> std::optional { + static_assert(std::same_as&&>); + return 99; + }); + REQUIRE(from_rvalue.has_value()); + REQUIRE(*from_rvalue == 99); + + auto from_const_rvalue + = std::move(std::as_const(engaged)).and_then([](auto&& p) -> std::optional { + static_assert(std::same_as const&&>); + return 99; + }); + REQUIRE(from_const_rvalue.has_value()); + REQUIRE(*from_const_rvalue == 99); + } + + // transform: engaged and disengaged cases + { + auto transformed = engaged.transform([](tcb::pointer& p) { return *p * 2; }); + + static_assert(std::same_as>); + REQUIRE(transformed.has_value()); + REQUIRE(*transformed == 84); + + bool called = false; + auto empty_result = disengaged.transform([&](auto&) { + called = true; + return 99; + }); + + REQUIRE(not called); + REQUIRE(not empty_result.has_value()); + } + + // transform: const lvalue, rvalue, and const rvalue overloads + { + auto from_const_lvalue = std::as_const(engaged).transform([](auto&& p) { + static_assert(std::same_as const&>); + return 99; + }); + REQUIRE(from_const_lvalue == std::optional{99}); + + auto from_rvalue = Opt(tcb::pointer_to_mut(value)).transform([](auto&& p) { + static_assert(std::same_as&&>); + return 99; + }); + REQUIRE(from_rvalue == std::optional{99}); + + auto from_const_rvalue = std::move(std::as_const(engaged)).transform([](auto&& p) { + static_assert(std::same_as const&&>); + return 99; + }); + REQUIRE(from_const_rvalue == std::optional{99}); + } + + // or_else: fallback is called only for disengaged optionals + { + bool called = false; + + auto present = engaged.or_else([&] { + called = true; + return Opt(tcb::pointer_to_mut(value)); + }); + + REQUIRE(not called); + REQUIRE(present.has_value()); + REQUIRE(present->to_address() == &value); + + auto absent = disengaged.or_else([&] { + called = true; + return Opt(tcb::pointer_to_mut(value)); + }); + + REQUIRE(called); + REQUIRE(absent.has_value()); + REQUIRE(absent->to_address() == &value); + } + + // or_else: rvalue optionals preserve their value + { + auto result = Opt(tcb::pointer_to_mut(value)).or_else([] { return Opt{}; }); + + REQUIRE(result.has_value()); + REQUIRE(result->to_address() == &value); + } } return true; @@ -1679,6 +2000,10 @@ int main() b = test_slice(); REQUIRE(b); + // unchecked slice tests + b = test_unchecked_slice(); + REQUIRE(b); + // array pointer tests b = test_array_pointer(); REQUIRE(b);