From 0e801dcdcb8d0db51f8e0558e7d4dddb24d3a340 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Mon, 7 Sep 2026 17:31:02 +0100 Subject: [PATCH 01/19] Implement op== using std::ranges::equal_to `std::ranges::equal_to` uses the "implementation-defined strict weak ordering over pointers", but returns a boolean directly rather than needing to do `compare_three_way(a, b) == 0` --- include/tcb/pointer.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index ad6cafa..7fe50b4 100644 --- a/include/tcb/pointer.hpp +++ b/include/tcb/pointer.hpp @@ -168,7 +168,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 @@ -281,7 +281,10 @@ struct TCB_PTR_GSL_POINTER(V) pointer : detail::void_pointer_base { 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 { From 0e2d3212722c9d975b06729c5bd51eb23c4b3f0e Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Wed, 9 Sep 2026 13:22:06 +0100 Subject: [PATCH 02/19] Add unchecked_slice As the name suggests, this is just like `slice` except that it does no bounds checking -- so most of its operations cause UB if their preconditions are not met. Its iterators are just raw pointers. There is no `unchecked_slice::at()`, because it's not clear what semantics it should have. Like `slice`, `unchecked_slice` is not publicly copyable or movable. --- include/tcb/pointer.hpp | 104 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 3 deletions(-) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index 7fe50b4..5336504 100644 --- a/include/tcb/pointer.hpp +++ b/include/tcb/pointer.hpp @@ -459,11 +459,106 @@ constexpr auto make_end_iterator(T* addr, std::size_t size) -> contiguous_iterat } // 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: + 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 + { + return std::ranges::equal(lhs, rhs); + } + + friend constexpr auto operator<=>(unchecked_slice const& lhs, unchecked_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); + } +}; + +// MARK: Slice + +template +struct slice { private: T* addr_; std::size_t sz_; @@ -849,6 +944,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; From 50135cb94383e24c7d9fbf7f894f5f993f4b00c4 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Wed, 9 Sep 2026 14:35:28 +0100 Subject: [PATCH 03/19] Implement slice using unchecked_slice This gives `slice` a public member variable of type `unchecked_slice`. The idea is that bounds checking is the default, but users can explicitly opt out where necessary, in a way that is clear and obvious in source code. For example: auto& slice = *ptr; int i = slice[10]; // bounds checked int j = slice.unchecked[10]; // explicitly not bounds checked my_algo(slice.begin(), slice.end()); // bounds checked iters my_algo(slice.unchecked.begin(), slice.unchecked.end()); // explicitly unchecked my_range_algo(slice); // uses checked iters my_range_algo(slice.unchecked); // uses unchecked iters --- include/tcb/pointer.hpp | 89 ++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 49 deletions(-) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index 5336504..3655497 100644 --- a/include/tcb/pointer.hpp +++ b/include/tcb/pointer.hpp @@ -559,14 +559,14 @@ struct TCB_PTR_GSL_POINTER(T) unchecked_slice { template struct slice { -private: - T* addr_; - std::size_t sz_; + 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; @@ -586,83 +586,92 @@ struct slice { 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 detail::make_begin_iterator(unchecked.addr_, unchecked.sz_); + } constexpr auto begin() const -> const_iterator { - return detail::make_begin_iterator(addr_, sz_); + return detail::make_begin_iterator(unchecked.addr_, unchecked.sz_); } 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 detail::make_end_iterator(unchecked.addr_, unchecked.sz_); + } + constexpr auto end() const -> const_iterator + { + return detail::make_end_iterator(unchecked.addr_, unchecked.sz_); + } constexpr auto cend() const -> const_iterator { return end(); } constexpr auto rbegin() -> reverse_iterator { return reverse_iterator(end()); } @@ -681,29 +690,11 @@ struct slice { friend constexpr auto operator==(slice const& lhs, slice const& rhs) -> bool requires std::equality_comparable - { - return std::ranges::equal(lhs, rhs); - } + = 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); - } + = default; }; // MARK: Array pointer @@ -786,7 +777,7 @@ struct TCB_PTR_GSL_POINTER(T) pointer { } void operator->() const&& = delete; - constexpr explicit operator bool() const noexcept { return slice_.addr_ != nullptr; } + constexpr explicit operator bool() const noexcept { return slice_.data() != nullptr; } friend constexpr auto operator==(pointer const& lhs, pointer const& rhs) -> bool { From 3da1edf9dc3b8a8ab1a6c1dcdee7fda39e601f15 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Wed, 9 Sep 2026 14:45:58 +0100 Subject: [PATCH 04/19] Add unchecked_slice tests --- tests/pointer.test.cpp | 116 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 3 deletions(-) diff --git a/tests/pointer.test.cpp b/tests/pointer.test.cpp index c0bebc0..c3c92f6 100755 --- a/tests/pointer.test.cpp +++ b/tests/pointer.test.cpp @@ -703,10 +703,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 +747,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; @@ -891,6 +891,112 @@ 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); + } + } + + return true; +} +static_assert(test_unchecked_slice()); + /* * MARK: array ptr tests */ @@ -1679,6 +1785,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); From e36aac33d603c035f7d6352f53db66b386a4a29e Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Wed, 9 Sep 2026 14:56:13 +0100 Subject: [PATCH 05/19] Always use checked iterators for slices This removes the option to define TCB_PTR_USE_UNCHECKED_ITERATORS, as users can now say `slice.unchecked.begin()` etc. --- include/tcb/pointer.hpp | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index 3655497..74c35a1 100644 --- a/include/tcb/pointer.hpp +++ b/include/tcb/pointer.hpp @@ -427,34 +427,17 @@ 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 +constexpr auto make_begin_iterator(T* addr, std::size_t size) -> checked_iterator { -#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 +constexpr auto make_end_iterator(T* addr, std::size_t size) -> checked_iterator { -#ifndef TCB_PTR_USE_UNCHECKED_ITERATORS return checked_iterator(addr, static_cast(size), static_cast(size)); -#else - return addr + size; -#endif } } // namespace detail @@ -579,8 +562,8 @@ struct 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; @@ -971,8 +954,8 @@ class optional> { public: using value_type = tcb::pointer; - using iterator = tcb::detail::contiguous_iterator_t; - using const_iterator = tcb::detail::contiguous_iterator_t; + using iterator = tcb::detail::checked_iterator; + using const_iterator = tcb::detail::checked_iterator; /* * Constructors From 556d1ad715c25881ca37d43a6c7890f6f195bf83 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Wed, 9 Sep 2026 15:59:53 +0100 Subject: [PATCH 06/19] Reorder op<=> fallback operations in slice For many types, `operator==` is cheaper than `operator<` so try that first if the element type does not support spaceship directly. --- include/tcb/pointer.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index 74c35a1..087648f 100644 --- a/include/tcb/pointer.hpp +++ b/include/tcb/pointer.hpp @@ -524,12 +524,12 @@ struct TCB_PTR_GSL_POINTER(T) unchecked_slice { if constexpr (std::three_way_comparable) { return lhs <=> rhs; } else { - if (lhs < rhs) { + if (lhs == rhs) { + return std::weak_ordering::equivalent; + } 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::weak_ordering::greater; } } }; From 8caab22eba398f132a0bb261bd7fdfb9d0c1de52 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Wed, 9 Sep 2026 16:51:36 +0100 Subject: [PATCH 07/19] Use static functions for checked iter contruction ...and make the "position" constructor private --- include/tcb/pointer.hpp | 61 +++++++++++++++++++++++------------------ tests/pointer.test.cpp | 16 +++++------ 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index 087648f..afd6ba3 100644 --- a/include/tcb/pointer.hpp +++ b/include/tcb/pointer.hpp @@ -305,22 +305,38 @@ struct TCB_PTR_GSL_POINTER(T) checked_iterator { friend struct checked_iterator>; + constexpr explicit checked_iterator(T* start, std::ptrdiff_t pos, std::ptrdiff_t size) + : start_(start), pos_(pos), size_(size) + { + if (pos_ < 0 || pos_ > size_) { + TCB_PTR_RUNTIME_ERROR("Bad size or position in checked_iterator ctor"); + } + } + + 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, static_cast(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, static_cast(buf.size), + static_cast(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_) @@ -427,19 +443,6 @@ struct TCB_PTR_GSL_POINTER(T) checked_iterator { -> std::strong_ordering = default; }; -template -constexpr auto make_begin_iterator(T* addr, std::size_t size) -> checked_iterator -{ - return checked_iterator(addr, 0, static_cast(size)); -} - -template -constexpr auto make_end_iterator(T* addr, std::size_t size) -> checked_iterator -{ - return checked_iterator(addr, static_cast(size), - static_cast(size)); -} - } // namespace detail // MARK: Unchecked slice @@ -639,21 +642,21 @@ struct slice { constexpr auto begin() -> iterator { - return detail::make_begin_iterator(unchecked.addr_, unchecked.sz_); + return iterator::to_start_of({.start_addr = data(), .size = size()}); } constexpr auto begin() const -> const_iterator { - return detail::make_begin_iterator(unchecked.addr_, unchecked.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(unchecked.addr_, unchecked.sz_); + return iterator::to_end_of({.start_addr = data(), .size = size()}); } constexpr auto end() const -> const_iterator { - return detail::make_end_iterator(unchecked.addr_, unchecked.sz_); + return const_iterator::to_end_of({.start_addr = data(), .size = size()}); } constexpr auto cend() const -> const_iterator { return end(); } @@ -1114,22 +1117,26 @@ class optional> { */ 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}); } /* diff --git a/tests/pointer.test.cpp b/tests/pointer.test.cpp index c3c92f6..76fcdaa 100755 --- a/tests/pointer.test.cpp +++ b/tests/pointer.test.cpp @@ -597,8 +597,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 +609,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 +624,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 +636,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,8 +655,8 @@ 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); From 3939491f647753a9649cf63ee62813a495794e94 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Wed, 9 Sep 2026 17:34:50 +0100 Subject: [PATCH 08/19] Simplify checked_iterator The original goal of checked_iterator was to prevent any attempt to form an out-of-bounds iterator. This is certainly safe, but ends up doing more checks than we actually need to prevent UB. With this change, we instead perform bounds checks only when dereferencing (or attempting to form a pointer-to-element). This means that it's now possible to form an out-of-bounds iterator, so long as you never actually try to access the element it would be pointing at. --- include/tcb/pointer.hpp | 54 +++++++++++++++++------------------------ tests/pointer.test.cpp | 40 +++++++++++++++--------------- 2 files changed, 42 insertions(+), 52 deletions(-) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index afd6ba3..991628a 100644 --- a/include/tcb/pointer.hpp +++ b/include/tcb/pointer.hpp @@ -300,17 +300,14 @@ 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::ptrdiff_t pos, std::ptrdiff_t size) - : start_(start), pos_(pos), size_(size) + constexpr explicit checked_iterator(T* start, std::size_t pos, std::size_t size) + : start_(start), pos_(pos), sz_(size) { - if (pos_ < 0 || pos_ > size_) { - TCB_PTR_RUNTIME_ERROR("Bad size or position in checked_iterator ctor"); - } } struct buffer_t { @@ -326,20 +323,19 @@ struct TCB_PTR_GSL_POINTER(T) checked_iterator { static constexpr auto to_start_of(buffer_t buf) -> checked_iterator { - return checked_iterator(buf.start_addr, 0, static_cast(buf.size)); + return checked_iterator(buf.start_addr, 0, buf.size); } static constexpr auto to_end_of(buffer_t buf) -> checked_iterator { - return checked_iterator(buf.start_addr, static_cast(buf.size), - static_cast(buf.size)); + 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_) { } @@ -351,27 +347,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; } @@ -385,9 +384,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; } @@ -401,19 +397,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; } @@ -435,7 +425,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; diff --git a/tests/pointer.test.cpp b/tests/pointer.test.cpp index 76fcdaa..b1e11b6 100755 --- a/tests/pointer.test.cpp +++ b/tests/pointer.test.cpp @@ -661,20 +661,20 @@ bool test_checked_iterator_bounds_checking() // 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 +683,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]); From a1d451070b74a1e5d800ddfaec28dc82ae8f1ad6 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Wed, 9 Sep 2026 18:51:59 +0100 Subject: [PATCH 09/19] Avoid deprecated array comparisons if possible Hopefully this will keep MSVC happy --- include/tcb/pointer.hpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index 991628a..566e245 100644 --- a/include/tcb/pointer.hpp +++ b/include/tcb/pointer.hpp @@ -443,6 +443,9 @@ struct TCB_PTR_GSL_POINTER(T) slice; TCB_PTR_EXPORT template 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_; @@ -505,13 +508,13 @@ struct TCB_PTR_GSL_POINTER(T) unchecked_slice { 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 + 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 + requires(std::totally_ordered && !std::is_array_v) { auto cmp = [](const_reference lhs, const_reference rhs) { if constexpr (std::three_way_comparable) { @@ -665,11 +668,11 @@ struct 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 + requires(std::equality_comparable && !std::is_array_v) = default; friend constexpr auto operator<=>(slice const& lhs, slice const& rhs) - requires std::totally_ordered + requires(std::totally_ordered && !std::is_array_v) = default; }; From 16424b7a873d63d5381fba97c5405377b3d61cd2 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Wed, 9 Sep 2026 20:10:21 +0100 Subject: [PATCH 10/19] Ensure TCB_PTR_RUNTIME_ERROR is always defined --- include/tcb/pointer.hpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index 566e245..95f842b 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 From 9e20004103eec848727586aa527aca2219791023 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Thu, 10 Sep 2026 12:21:59 +0100 Subject: [PATCH 11/19] Allow null state in pointer The object pointer and void pointer specialisations of `tcb::pointer` do not permit a null state -- this is an important design goal for safety, and what allows us to re-use the null representation for our `optional` specialisation. The intention was that this would apply to the array specialisation as well -- so it had no default constructor, and attempting to pass `nullptr` to `from_address_with_size()` was a runtime error. Unfortunately however, I didn't consider the case of forming an array pointer to a default-constructed vector, which usually has a null data pointer. This means that it's already possible to legitimately get a null `ptr`, which in turn means that `optional>` can't tell the difference between an empty optional and one initialized from a default-constructed vector. That's bad. This means that unfortunately we can no longer use our optional specialisation for array pointers, and they will instead use the primary optional template. We also may as well explicitly acknowledge the null state by adding a default constructor and allowing a (suitably typed) null pointer to be passed to `from_address_with_size()`, provided the given size is zero. This is a bit of a shame, but it's worth noting that this is not a safety problem -- unlike with object pointers, a default-constructed array pointer will deref to a valid, useable (if zero-sized) `slice`. We still maintain the invariant that if `data()` is null then `size()` must be zero, which is the important bit from a safety point of view. --- include/tcb/pointer.hpp | 13 ++++++------ tests/pointer.test.cpp | 44 +++++++++++++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index 95f842b..b9277ed 100644 --- a/include/tcb/pointer.hpp +++ b/include/tcb/pointer.hpp @@ -697,11 +697,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) { @@ -723,12 +718,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 @@ -948,6 +946,7 @@ struct hash> { // MARK: std::optional template + requires(!std::is_unbounded_array_v) class optional> { private: tcb::pointer ptr_; diff --git a/tests/pointer.test.cpp b/tests/pointer.test.cpp index b1e11b6..61c9dd2 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

); @@ -178,7 +185,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*>); } @@ -1144,11 +1151,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)); } @@ -1171,7 +1182,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)); } @@ -1182,6 +1197,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()); @@ -1731,10 +1749,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()); @@ -1749,6 +1766,17 @@ 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()); } return true; From 63e7eb1b70511d4b144261511d4647d6b409ccc3 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Thu, 10 Sep 2026 13:01:46 +0100 Subject: [PATCH 12/19] Remove bool conversion operators The single-object and void specialisations have the invariant that they're never null, so converting to bool would always return true. For array pointers, a pointer to a null array is still safe to dereference, so it's not clear that the value of the bool conversion operator is -- users can always test `data() == nullptr` themselves if they need want to. --- include/tcb/pointer.hpp | 8 +------- tests/pointer.test.cpp | 10 ---------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index b9277ed..a119e09 100644 --- a/include/tcb/pointer.hpp +++ b/include/tcb/pointer.hpp @@ -158,8 +158,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 @@ -283,8 +281,6 @@ 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 std::ranges::equal_to{}(lhs.addr_, rhs.addr_); @@ -758,8 +754,6 @@ struct TCB_PTR_GSL_POINTER(T) pointer { } void operator->() const&& = delete; - constexpr explicit operator bool() const noexcept { return slice_.data() != nullptr; } - friend constexpr auto operator==(pointer const& lhs, pointer const& rhs) -> bool { return lhs->data() == rhs->data() && lhs->size() == rhs->size(); @@ -1186,7 +1180,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& diff --git a/tests/pointer.test.cpp b/tests/pointer.test.cpp index 61c9dd2..0974558 100755 --- a/tests/pointer.test.cpp +++ b/tests/pointer.test.cpp @@ -169,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) { @@ -350,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; From 924daeab25ae489c12d0628a250558ea35a0227d Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Thu, 10 Sep 2026 15:44:39 +0100 Subject: [PATCH 13/19] Add some slice comparison static asserts --- tests/pointer.test.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/pointer.test.cpp b/tests/pointer.test.cpp index 0974558..690fe56 100755 --- a/tests/pointer.test.cpp +++ b/tests/pointer.test.cpp @@ -757,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 @@ -854,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 @@ -988,6 +1013,21 @@ constexpr bool test_unchecked_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>); + } } return true; From abdec619be52da949aa42db9acc9c92851433d2f Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Thu, 10 Sep 2026 16:04:20 +0100 Subject: [PATCH 14/19] Provide optional iterators only in C++26 mode ...or more specifically, when the standard library advertises optional range support, because it's a bit weird for a specialisation to add a feature that the primary template doesn't have. Defining `TCB_PTR_OPTIONAL_RANGE_SUPPORT` adds `begin()` and `end()` to our optional specialisation in all language modes. --- include/tcb/pointer.hpp | 11 +++++++++++ tests/pointer.config.hpp | 2 ++ 2 files changed, 13 insertions(+) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index a119e09..956f5a2 100644 --- a/include/tcb/pointer.hpp +++ b/include/tcb/pointer.hpp @@ -110,6 +110,12 @@ DEALINGS IN THE SOFTWARE. # define TCB_PTR_THROW(ex) TCB_PTR_RUNTIME_ERROR(ex.what()) #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 @@ -947,8 +953,11 @@ class optional> { public: using value_type = tcb::pointer; + +#ifdef TCB_PTR_OPTIONAL_RANGE_SUPPORT using iterator = tcb::detail::checked_iterator; using const_iterator = tcb::detail::checked_iterator; +#endif /* * Constructors @@ -1105,6 +1114,7 @@ class optional> { /* * Iterator support */ +#ifdef TCB_PTR_OPTIONAL_RANGE_SUPPORT constexpr auto begin() noexcept -> iterator { return iterator::to_start_of( @@ -1128,6 +1138,7 @@ class optional> { return const_iterator::to_end_of( {.start_addr = std::addressof(ptr_), .size = has_value() ? 1u : 0u}); } +#endif /* * Observers diff --git a/tests/pointer.config.hpp b/tests/pointer.config.hpp index b777493..a4e778e 100644 --- a/tests/pointer.config.hpp +++ b/tests/pointer.config.hpp @@ -10,3 +10,5 @@ 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_RANGE_SUPPORT 1 From 0dbd98b33770372ae0043def6a179ef62382caa4 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Thu, 10 Sep 2026 16:48:07 +0100 Subject: [PATCH 15/19] Correctly constrain optional monadic operations "the program is ill-formed" == static_assert "does not participate in overload resolution" = requires clause --- include/tcb/pointer.hpp | 115 +++++++++++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 31 deletions(-) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index 956f5a2..792fd56 100644 --- a/include/tcb/pointer.hpp +++ b/include/tcb/pointer.hpp @@ -943,6 +943,20 @@ 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 @@ -1227,9 +1241,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 { @@ -1238,9 +1253,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 { @@ -1252,35 +1268,44 @@ class optional> { * Monadic operations */ 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{}; } } @@ -1288,61 +1313,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 { @@ -1351,9 +1402,11 @@ 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 { From ee583f768d3e282d9257c019559203f8aec23dbb Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Thu, 10 Sep 2026 16:53:51 +0100 Subject: [PATCH 16/19] Provide optional monadic ops only in C++23 mode ...or more precisely, when the standard library advertises support for them. Again, this can be overridden by defining `TCB_PTR_OPTIONAL_MONADIC_SUPPORT`, in which case the operations will be provided in all language modes --- include/tcb/pointer.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index 792fd56..59a1618 100644 --- a/include/tcb/pointer.hpp +++ b/include/tcb/pointer.hpp @@ -110,6 +110,12 @@ 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 @@ -1267,6 +1273,7 @@ class optional> { /* * Monadic operations */ +#ifdef TCB_PTR_OPTIONAL_MONADIC_SUPPORT template constexpr auto and_then(F&& f) & { @@ -1413,6 +1420,7 @@ class optional> { return std::invoke(static_cast(f)); } } +#endif // TCB_PTR_OPTIONAL_MONADIC_SUPPORT /* * Modifiers From d1a357207d51fcbba73307ce2b3651d9e8684e9d Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Thu, 10 Sep 2026 17:19:36 +0100 Subject: [PATCH 17/19] Add tests for optional monadic ops --- tests/pointer.config.hpp | 1 + tests/pointer.test.cpp | 131 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/tests/pointer.config.hpp b/tests/pointer.config.hpp index a4e778e..cb5bf60 100644 --- a/tests/pointer.config.hpp +++ b/tests/pointer.config.hpp @@ -11,4 +11,5 @@ struct ptr_runtime_error : std::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 690fe56..2ced906 100755 --- a/tests/pointer.test.cpp +++ b/tests/pointer.test.cpp @@ -1809,6 +1809,137 @@ constexpr bool test_std_optional_specialisation() 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; } static_assert(test_std_optional_specialisation()); From d575b400b415dcb5270710ce3aff1a1282c094c1 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Thu, 10 Sep 2026 17:56:31 +0100 Subject: [PATCH 18/19] Fix typo in optional::value_or() static assert ...and also add a test for value_or() --- include/tcb/pointer.hpp | 2 +- tests/pointer.test.cpp | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/include/tcb/pointer.hpp b/include/tcb/pointer.hpp index 59a1618..3ae3fab 100644 --- a/include/tcb/pointer.hpp +++ b/include/tcb/pointer.hpp @@ -1249,7 +1249,7 @@ class optional> { template > constexpr auto value_or(U&& default_value) const& -> tcb::pointer { - static_assert(std::is_convertible_v>); + static_assert(std::is_convertible_v>); if (has_value()) { return ptr_; diff --git a/tests/pointer.test.cpp b/tests/pointer.test.cpp index 2ced906..372a839 100755 --- a/tests/pointer.test.cpp +++ b/tests/pointer.test.cpp @@ -1746,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>; From 9a5ec27aea220cf1362bed7a82368b9e0473b3d8 Mon Sep 17 00:00:00 2001 From: Tristan Brindle Date: Thu, 10 Sep 2026 18:36:05 +0100 Subject: [PATCH 19/19] Add unchecked slice example --- examples/06_slices.cpp | 2 +- examples/07_unchecked_slices.cpp | 63 ++++++++++++++++++++++++++++++++ examples/CMakeLists.txt | 1 + 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 examples/07_unchecked_slices.cpp 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)