From 0d9e9812a8b4636b97cdcda91aadf3ee25cc00e5 Mon Sep 17 00:00:00 2001 From: Edward Nolan Date: Fri, 18 Sep 2026 19:34:19 -0400 Subject: [PATCH] Add random access support for UTF-32-to-UTF-32 self-transcode --- include/beman/utf_view/to_utf_view.hpp | 100 +++++++++++++++- papers/P2728.md | 67 ++++++++++- .../utf_view/std_archetypes/iterator.hpp | 109 ++++++++++++++++++ .../utf_view/std_archetypes/iterator.test.cpp | 2 + tests/beman/utf_view/to_utf_view.test.cpp | 104 +++++++++++++++++ 5 files changed, 374 insertions(+), 8 deletions(-) diff --git a/include/beman/utf_view/to_utf_view.hpp b/include/beman/utf_view/to_utf_view.hpp index 008f51d6..981a3688 100644 --- a/include/beman/utf_view/to_utf_view.hpp +++ b/include/beman/utf_view/to_utf_view.hpp @@ -69,14 +69,18 @@ namespace detail { return 0xDC00 <= c && c <= 0xDFFF; } - template + template struct iter_category_impl {}; - template - struct iter_category_impl { + template + struct iter_category_impl { static consteval auto impl() { using category = typename std::iterator_traits>::iterator_category; - if constexpr (std::derived_from) { + if constexpr (std::derived_from + && std::same_as> + && std::same_as) { + return std::random_access_iterator_tag{}; + } else if constexpr (std::derived_from) { return std::bidirectional_iterator_tag{}; } else if constexpr (std::derived_from) { return std::forward_iterator_tag{}; @@ -265,7 +269,8 @@ template /* PAPER: class to_utf_view::@*iterator*@ { */ /* !PAPER */ -struct to_utf_view::exposition_only_iterator : detail::iter_category_impl { +struct to_utf_view::exposition_only_iterator + : detail::iter_category_impl { /* PAPER */ private: using exposition_only_Base = exposition_only_maybe_const; // @*exposition only*@ @@ -274,7 +279,11 @@ struct to_utf_view::exposition_only_iterator : detail::iter_catego using from_type = std::ranges::range_value_t; // @*exposition only*@ static consteval auto iter_concept_impl() { - if constexpr (std::ranges::bidirectional_range) { + if constexpr (std::ranges::random_access_range + && std::same_as + && std::same_as) { + return std::random_access_iterator_tag{}; + } else if constexpr (std::ranges::bidirectional_range) { return std::bidirectional_iterator_tag{}; } else if constexpr (std::ranges::forward_range) { return std::forward_iterator_tag{}; @@ -454,6 +463,85 @@ struct to_utf_view::exposition_only_iterator : detail::iter_catego return lhs.exposition_only_current_ == rhs.exposition_only_current_ && lhs.exposition_only_buf_index_ == rhs.exposition_only_buf_index_; } + friend constexpr auto operator<=>( + exposition_only_iterator const& lhs, exposition_only_iterator const& rhs) + requires std::ranges::random_access_range + && std::same_as> + && std::same_as + { + return lhs.exposition_only_current_ <=> rhs.exposition_only_current_; + } + + friend constexpr exposition_only_iterator& operator+=( + exposition_only_iterator& it, difference_type const n) + requires std::ranges::random_access_range + && std::same_as> + && std::same_as + { + it.exposition_only_current_ += n; + it.exposition_only_reseat(); + return it; + } + + friend constexpr exposition_only_iterator operator+( + exposition_only_iterator it, difference_type const n) + requires std::ranges::random_access_range + && std::same_as> + && std::same_as + { + it += n; + return it; + } + + friend constexpr exposition_only_iterator operator+( + difference_type const n, exposition_only_iterator it) + requires std::ranges::random_access_range + && std::same_as> + && std::same_as + { + it += n; + return it; + } + + friend constexpr exposition_only_iterator& operator-=( + exposition_only_iterator& it, difference_type const n) + requires std::ranges::random_access_range + && std::same_as> + && std::same_as + { + it.exposition_only_current_ -= n; + it.exposition_only_reseat(); + return it; + } + + friend constexpr exposition_only_iterator operator-( + exposition_only_iterator it, difference_type const n) + requires std::ranges::random_access_range + && std::same_as> + && std::same_as + { + it -= n; + return it; + } + + friend constexpr difference_type operator-( + exposition_only_iterator const& lhs, + exposition_only_iterator const& rhs) + requires std::ranges::random_access_range + && std::same_as> + && std::same_as + { + return lhs.exposition_only_current_ - rhs.exposition_only_current_; + } + + constexpr value_type operator[](difference_type const n) const + requires std::ranges::random_access_range + && std::same_as> + && std::same_as + { + return *(*this + n); + } + private: /* !PAPER */ diff --git a/papers/P2728.md b/papers/P2728.md index cddacdaf..e9f8d246 100644 --- a/papers/P2728.md +++ b/papers/P2728.md @@ -888,6 +888,61 @@ public: return lhs.@*current_*@ == rhs.@*current_*@ && lhs.@*buf_index_*@ == rhs.@*buf_index_*@; } + friend constexpr auto operator<=>(@*iterator*@ const& lhs, @*iterator*@ const& rhs) + requires random_access_range<@*Base*@> && same_as> && same_as + { + return lhs.@*current_*@ <=> rhs.@*current_*@; + } + + friend constexpr @*iterator*@& operator+=(@*iterator*@& it, difference_type const n) + requires random_access_range<@*Base*@> && same_as> && same_as + { + it.@*current_*@ += n; + it.@*reseat*@(); + return it; + } + + friend constexpr @*iterator*@ operator+(@*iterator*@ it, difference_type const n) + requires random_access_range<@*Base*@> && same_as> && same_as + { + it += n; + return it; + } + + friend constexpr @*iterator*@ operator+(difference_type const n, @*iterator*@ it) + requires random_access_range<@*Base*@> && same_as> && same_as + { + it += n; + return it; + } + + friend constexpr @*iterator*@& operator-=(@*iterator*@& it, difference_type const n) + requires random_access_range<@*Base*@> && same_as> && same_as + { + it.@*current_*@ -= n; + it.@*reseat*@(); + return it; + } + + friend constexpr @*iterator*@ operator-(@*iterator*@ it, difference_type const n) + requires random_access_range<@*Base*@> && same_as> && same_as + { + it -= n; + return it; + } + + friend constexpr difference_type operator-(@*iterator*@ const& lhs, @*iterator*@ const& rhs) + requires random_access_range<@*Base*@> && same_as> && same_as + { + return lhs.@*current_*@ - rhs.@*current_*@; + } + + constexpr value_type operator[](difference_type const n) const + requires random_access_range<@*Base*@> && same_as> && same_as + { + return *(*this + n); + } + private: constexpr sentinel_t<@*Base*@> @*end*@() const { // @*exposition only*@ return @*end_*@; @@ -950,7 +1005,10 @@ point. `to_utf_view::@*iterator*@::iterator_concept` is defined as follows: -- If `V` models `bidirectional_range`, then `iterator_concept` is `bidirectional_iterator_tag`. +- If `V` models `random_access_range`, and `range_value_t` is `char32_t`, + and `ToType` is `char32_t`, then `iterator_concept` is + `random_access_iterator_tag`. +- Otherwise, if `V` models `bidirectional_range`, then `iterator_concept` is `bidirectional_iterator_tag`. - Otherwise, if `V` models `forward_range`, then `iterator_concept` is `forward_iterator_tag`. - Otherwise, `iterator_concept` is `input_iterator_tag`. @@ -959,7 +1017,10 @@ The member *typedef-name* `iterator_category` is defined if and only if `V` mode In that case, `to_utf_view::@*iterator*@::iterator_category` is defined as follows: - Let `C` denote the type `iterator_traits>::iterator_category`. -- If `C` models `derived_from`, then `iterator_category` denotes `bidirectional_iterator_tag`. +- If `C` models `derived_from`, and + `range_value_t` is `char32_t`, and `ToType` is `char32_t`, then + `iterator_category` denotes `random_access_iterator_tag`. +- Otherwise, if `C` models `derived_from`, then `iterator_category` denotes `bidirectional_iterator_tag`. - Otherwise, if `C` models `derived_from`, then `iterator_category` denotes `forward_iterator_tag`. - Otherwise, `iterator_category` denotes `C`. @@ -1311,6 +1372,8 @@ But we voted in favor of providing it for forward ranges: - Revert SIMD support and add design discussion of why it was rejected - Rename `to_utf_view_error_kind` to `to_utf_view_kind` per SG9 discussion - Implement `it.base_code_units()` for forward ranges per SG9 feedback +- Add random access support for UTF-32-to-UTF-32 self-transcode per SG9 + feedback ## Changes since R13 diff --git a/tests/beman/utf_view/std_archetypes/iterator.hpp b/tests/beman/utf_view/std_archetypes/iterator.hpp index 180fc5c3..d9f89f92 100644 --- a/tests/beman/utf_view/std_archetypes/iterator.hpp +++ b/tests/beman/utf_view/std_archetypes/iterator.hpp @@ -208,6 +208,115 @@ struct basic_bidirectional_iterator_archetype { using bidirectional_iterator_archetype = basic_bidirectional_iterator_archetype; +template , + typename ReferenceType = detail::reference_type_archetype> +struct basic_random_access_iterator_archetype { + using value_type = ValueType; + using reference_type = ReferenceType; + using difference_type = std::ptrdiff_t; + using iterator_concept = std::random_access_iterator_tag; + constexpr basic_random_access_iterator_archetype() + : x{} { } + constexpr explicit basic_random_access_iterator_archetype(State x_in) + : x{std::move(x_in)} { } + basic_random_access_iterator_archetype(basic_random_access_iterator_archetype const&) = + default; + basic_random_access_iterator_archetype& operator=( + basic_random_access_iterator_archetype const&) = default; + basic_random_access_iterator_archetype(basic_random_access_iterator_archetype&&) = + default; + basic_random_access_iterator_archetype& operator=( + basic_random_access_iterator_archetype&&) = default; + + constexpr reference_type operator*() const { + return reference_type{x}; + } + constexpr basic_random_access_iterator_archetype& operator++() { + ++x; + return *this; + } + constexpr basic_random_access_iterator_archetype operator++(int) { + basic_random_access_iterator_archetype result{*this}; + ++x; + return result; + } + constexpr basic_random_access_iterator_archetype& operator--() { + --x; + return *this; + } + constexpr basic_random_access_iterator_archetype operator--(int) { + basic_random_access_iterator_archetype result{*this}; + --x; + return result; + } + + friend constexpr boolean_testable_archetype operator==( + basic_random_access_iterator_archetype const& lhs, + basic_random_access_iterator_archetype const& rhs) { + return boolean_testable_archetype{lhs.x == rhs.x}; + } + friend constexpr boolean_testable_archetype operator!=( + basic_random_access_iterator_archetype const& lhs, + basic_random_access_iterator_archetype const& rhs) { + return boolean_testable_archetype{lhs.x != rhs.x}; + } + friend constexpr auto operator<=>( + basic_random_access_iterator_archetype const& lhs, + basic_random_access_iterator_archetype const& rhs) { + return lhs.x <=> rhs.x; + } + + friend constexpr basic_random_access_iterator_archetype& operator+=( + basic_random_access_iterator_archetype& it, + std::ptrdiff_t const n) { + it.x += n; + return it; + } + + friend constexpr basic_random_access_iterator_archetype operator+( + basic_random_access_iterator_archetype it, + std::ptrdiff_t const n) { + it += n; + return it; + } + + friend constexpr basic_random_access_iterator_archetype operator+( + std::ptrdiff_t const n, + basic_random_access_iterator_archetype it) { + it += n; + return it; + } + + friend constexpr basic_random_access_iterator_archetype& operator-=( + basic_random_access_iterator_archetype& it, + std::ptrdiff_t const n) { + it.x -= n; + return it; + } + + friend constexpr basic_random_access_iterator_archetype operator-( + basic_random_access_iterator_archetype it, + std::ptrdiff_t const n) { + it -= n; + return it; + } + + friend constexpr std::ptrdiff_t operator-( + basic_random_access_iterator_archetype const& lhs, + basic_random_access_iterator_archetype const& rhs) { + return lhs.x - rhs.x; + } + + constexpr reference_type operator[](std::ptrdiff_t const n) const { + return *(*this + n); + } + + State x; +}; + +using random_access_iterator_archetype = + basic_random_access_iterator_archetype; + } // namespace beman::utf_view::tests::std_archetypes #endif // BEMAN_UTF_VIEW_TESTS_STD_ARCHETYPES_ITERATOR_HPP diff --git a/tests/beman/utf_view/std_archetypes/iterator.test.cpp b/tests/beman/utf_view/std_archetypes/iterator.test.cpp index 0eb8143f..55cffc08 100644 --- a/tests/beman/utf_view/std_archetypes/iterator.test.cpp +++ b/tests/beman/utf_view/std_archetypes/iterator.test.cpp @@ -21,5 +21,7 @@ static_assert(std::forward_iterator); static_assert(std::forward_iterator>); static_assert(std::bidirectional_iterator); static_assert(std::bidirectional_iterator>); +static_assert(std::random_access_iterator); +static_assert(std::random_access_iterator>); } // namespace beman::utf_view::tests::std_archetypes diff --git a/tests/beman/utf_view/to_utf_view.test.cpp b/tests/beman/utf_view/to_utf_view.test.cpp index 9beaed13..59ce43be 100644 --- a/tests/beman/utf_view/to_utf_view.test.cpp +++ b/tests/beman/utf_view/to_utf_view.test.cpp @@ -83,6 +83,21 @@ static_assert( std::ranges::subrange< test_bidi_iterator, test_bidi_iterator>, to_utf_view_kind::replacement, char8_t>>>); +static_assert( + std::random_access_iterator< + std::ranges::iterator_t< + to_utf_view< + std::ranges::subrange< + test_random_access_iterator, std::default_sentinel_t>, + to_utf_view_kind::replacement, char32_t>>>); +static_assert( + std::random_access_iterator< + std::ranges::sentinel_t< + to_utf_view< + std::ranges::subrange< + test_random_access_iterator, + test_random_access_iterator>, + to_utf_view_kind::replacement, char32_t>>>); // size() is only available when a UTF-32 -> UTF-32 view is transcoding a sized range. static_assert(std::ranges::sized_range< @@ -1746,6 +1761,92 @@ constexpr bool base_code_units_test() { return true; } +CONSTEXPR_UNLESS_MSVC bool utf32_self_transcode_test() { + using namespace std::string_view_literals; + constexpr auto test1{ + [](auto const cpo, auto const value) { + std::initializer_list const arr{ + {U'\u0051'}, {U'\u03D5'}, {U'\u5B66'}, {U'\x00021A87'}}; + test_random_access_iterator begin(arr); + test_random_access_iterator end(arr); + std::ranges::advance(end, arr.size()); + auto random_access_utf_view{std::ranges::subrange{begin, end} | cpo}; + auto it1{random_access_utf_view.begin()}; + auto it2{random_access_utf_view.begin()}; + if ((it1 <=> it2) != std::strong_ordering::equal) { + return false; + } + it1 += 2; + if (*it1 != value(U'\u5B66')) { + return false; + } + if ((it1 <=> it2) != std::strong_ordering::greater) { + return false; + } + if ((it2 + 2) != it1) { + return false; + } + if ((2 + it2) != it1) { + return false; + } + it2 += 2; + it1 -= 2; + if ((it1 <=> it2) != std::strong_ordering::less) { + return false; + } + if ((it2 - 2) != it1) { + return false; + } + if (it1[0] != value(U'\u0051')) { + return false; + } + if (it1[1] != value(U'\u03D5')) { + return false; + } + if (it1[2] != value(U'\u5B66')) { + return false; + } + if (it1[3] != value(U'\x00021A87')) { + return false; + } + if ((it1 + 4) != random_access_utf_view.end()) { + return false; + } + if (!std::ranges::empty((it1 + 4).base_code_units())) { + return false; + } + return true; + }}; + if (!test1(to_utf32, [](char32_t const c) { return c; })) { + return false; + } + if (!test1( + to_utf32_or_error, + [](char32_t const c){ + return std::expected{c}; + })) { + return false; + } + auto const from_u32sv{ + U"\u0051\u03D5\u5B66\x00021A87"sv + | to_utf32}; + static_assert( + std::is_same_v< + decltype(from_u32sv.begin())::iterator_category, + std::random_access_iterator_tag>); + auto const from_transformed_u32sv{ + U"\u0051\u03D5\u5B66\x00021A87"sv + | std::views::transform([](char32_t const c) { return c; }) + | to_utf32}; + static_assert( + std::is_same_v< + decltype(from_transformed_u32sv.begin())::iterator_category, + std::input_iterator_tag>); + static_assert( + std::ranges::random_access_range); + return true; +} + CONSTEXPR_UNLESS_MSVC bool utf_view_test() { if (!input_iterator_test(std::initializer_list{u8'x'})) { return false; @@ -1900,6 +2001,9 @@ CONSTEXPR_UNLESS_MSVC bool utf_view_test() { if (!base_code_units_test()) { return false; } + if (!utf32_self_transcode_test()) { + return false; + } return true; }