https://gcc.gnu.org/g:35f05d04f305dc51364469d20879dbea8810b0db
commit r16-5217-g35f05d04f305dc51364469d20879dbea8810b0db Author: Tomasz Kamiński <[email protected]> Date: Fri Nov 7 18:17:56 2025 +0100 libstdc++: Optimize handling of optional for views: take, drop, reverse and as_const. This implements P3913R1: Optimize for std::optional in range adaptors. Specifically, for an opt of type optional<T> that is a view: * views::reverse(opt), views::take(opt, n), and views::drop(opt, n) returns optional<T>. * views::as_const(opt), optional<T&> is converted into optional<const T&>. optional<T const> is not used in the non-reference case because, such type is not move assignable, and thus not a view. libstdc++-v3/ChangeLog: * include/std/optional (__is_optional_ref): Define. * include/std/ranges (_Take::operator(), _Drop::operator()) (_Reverse::operator()): Handle optional<T> that are view. (_AsConst::operator()): Handle optional<T&>. * testsuite/20_util/optional/range.cc: New tests. Reviewed-by: Jonathan Wakely <[email protected]> Signed-off-by: Tomasz Kamiński <[email protected]> Diff: --- libstdc++-v3/include/std/optional | 8 +- libstdc++-v3/include/std/ranges | 16 +++ libstdc++-v3/testsuite/20_util/optional/range.cc | 161 ++++++++++++++++++++++- 3 files changed, 179 insertions(+), 6 deletions(-) diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional index 75a9531ccd5d..41c04b10720e 100644 --- a/libstdc++-v3/include/std/optional +++ b/libstdc++-v3/include/std/optional @@ -1486,6 +1486,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template<typename _Tp> class optional<_Tp&>; + template<typename _Tp> + constexpr bool __is_optional_ref_v = false; + + template<typename _Tp> + constexpr bool __is_optional_ref_v<optional<_Tp&>> = true; + template<typename _Tp> struct __optional_ref_base {}; @@ -2187,7 +2193,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION constexpr bool ranges::enable_borrowed_range<optional<_Tp&>> = true; #endif - + template<typename _Tp> inline constexpr range_format format_kind<optional<_Tp>> = range_format::disabled; diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges index 158692d92a70..ae57b9a08098 100644 --- a/libstdc++-v3/include/std/ranges +++ b/libstdc++-v3/include/std/ranges @@ -2403,6 +2403,10 @@ namespace views::__adaptor using _Tp = remove_cvref_t<_Range>; if constexpr (__detail::__is_empty_view<_Tp>) return _Tp(); +#ifdef __cpp_lib_optional_range_support // >= C++26 + else if constexpr (__is_optional_v<_Tp> && view<_Tp>) + return __n ? std::forward<_Range>(__r) : _Tp(); +#endif else if constexpr (random_access_range<_Tp> && sized_range<_Tp> && (std::__detail::__is_span<_Tp> @@ -2679,6 +2683,10 @@ namespace views::__adaptor using _Tp = remove_cvref_t<_Range>; if constexpr (__detail::__is_empty_view<_Tp>) return _Tp(); +#ifdef __cpp_lib_optional_range_support // >= C++26 + else if constexpr (__is_optional_v<_Tp> && view<_Tp>) + return __n ? _Tp() : std::forward<_Range>(__r); +#endif else if constexpr (random_access_range<_Tp> && sized_range<_Tp> && (std::__detail::__is_span<_Tp> @@ -4156,6 +4164,10 @@ namespace views::__adaptor using _Tp = remove_cvref_t<_Range>; if constexpr (__detail::__is_reverse_view<_Tp>) return std::forward<_Range>(__r).base(); +#ifdef __cpp_lib_optional_range_support // >= C++26 + else if constexpr (__is_optional_v<_Tp> && view<_Tp>) + return std::forward<_Range>(__r); +#endif else if constexpr (__detail::__is_reversible_subrange<_Tp>) { using _Iter = decltype(ranges::begin(__r).base()); @@ -9312,6 +9324,10 @@ namespace views::__adaptor return views::all(std::forward<_Range>(__r)); else if constexpr (__detail::__is_empty_view<_Tp>) return views::empty<const element_type>; +#if __cpp_lib_optional >= 202506L && __cpp_lib_optional_range_support // >= C++26 + else if constexpr (__is_optional_ref_v<_Tp>) + return optional<const typename _Tp::value_type&>(__r); +#endif else if constexpr (std::__detail::__is_span<_Tp>) return span<const element_type, _Tp::extent>(std::forward<_Range>(__r)); else if constexpr (__detail::__is_constable_ref_view<_Tp>) diff --git a/libstdc++-v3/testsuite/20_util/optional/range.cc b/libstdc++-v3/testsuite/20_util/optional/range.cc index 981969cb6142..4238c9cf28ee 100644 --- a/libstdc++-v3/testsuite/20_util/optional/range.cc +++ b/libstdc++-v3/testsuite/20_util/optional/range.cc @@ -10,6 +10,26 @@ #include <testsuite_hooks.h> +struct NonMovable +{ + constexpr NonMovable() {} + constexpr NonMovable(int) {} + + NonMovable(NonMovable&&) = delete; + NonMovable& operator=(NonMovable&&) = delete; + + friend bool operator==(NonMovable const&, NonMovable const&) = default; +}; + +struct NonAssignable +{ + NonAssignable() = default; + NonAssignable(NonAssignable&&) = default; + NonAssignable& operator=(NonAssignable&&) = delete; + + friend bool operator==(NonAssignable const&, NonAssignable const&) = default; +}; + template<typename T> constexpr void @@ -24,10 +44,11 @@ test_range_concepts() constexpr bool is_ref_opt = std::is_reference_v<T>; static_assert(std::ranges::borrowed_range<O> == is_ref_opt); - // an optional<const T> is not assignable, and therefore does not satisfy ranges::view - constexpr bool is_const_opt = std::is_const_v<T>; - static_assert(std::ranges::view<O> == !is_const_opt); - static_assert(std::ranges::viewable_range<O> == !is_const_opt); + // for any T (including const U) such that optional<T> is not assignable, + // it does not satisfy ranges::view + constexpr bool is_opt_view = std::is_reference_v<T> || std::movable<T>; + static_assert(std::ranges::view<O> == is_opt_view); + static_assert(std::ranges::viewable_range<O> == is_opt_view); } @@ -108,7 +129,7 @@ test_non_empty(const T& value) ++count; VERIFY(count == 1); - if constexpr (!std::is_const_v<V> && !std::is_array_v<V>) { + if constexpr (std::is_move_assignable_v<V>) { for (auto& x : non_empty) x = V{}; VERIFY(non_empty); @@ -168,6 +189,99 @@ constexpr void test_not_range() static_assert(!requires(std::optional<T> o) { o.end(); }); }; +template<typename T> +constexpr bool is_optional = false; + +template<typename T> +constexpr bool is_optional<std::optional<T>> = true; + +template<bool usesOptional, typename T, typename U = std::remove_cv_t<T>> +constexpr void test_as_const(std::type_identity_t<U> u) +{ + std::optional<T> o(std::in_place, std::forward<U>(u)); + auto cv = std::views::as_const(o); + static_assert(is_optional<decltype(cv)> == usesOptional); + static_assert(std::is_same_v<decltype(*cv.begin()), const std::remove_reference_t<T>&>); + VERIFY(!std::ranges::empty(cv)); + + std::optional<T> e; + auto cve = std::views::as_const(e); + static_assert(is_optional<decltype(cve)> == usesOptional); + static_assert(std::is_same_v<decltype(*cve.begin()), const std::remove_reference_t<T>&>); + VERIFY(std::ranges::empty(cve)); +} + +template<bool usesOptional, typename T, typename U = std::remove_cv_t<T>> +constexpr void +test_reverse(std::type_identity_t<U> u) +{ + std::optional<T> o(std::in_place, std::forward<U>(u)); + auto rv = std::views::reverse(o); + static_assert(is_optional<decltype(rv)> == usesOptional); + static_assert(std::is_same_v<decltype(*rv.begin()), T&>); + VERIFY(!std::ranges::empty(rv)); + + std::optional<T> e; + auto rve = std::views::reverse(e); + static_assert(is_optional<decltype(rve)> == usesOptional); + static_assert(std::is_same_v<decltype(*rve.begin()), T&>); + VERIFY(std::ranges::empty(rve)); +} + +template<bool usesOptional, typename T, typename U = std::remove_cv_t<T>> +constexpr void +test_take(std::type_identity_t<U> u) +{ + std::optional<T> o(std::in_place, std::forward<U>(u)); + auto tvp = std::views::take(o, 3); + static_assert(is_optional<decltype(tvp)> == usesOptional); + static_assert(std::is_same_v<decltype(*tvp.begin()), T&>); + VERIFY(!std::ranges::empty(tvp)); + + auto tvz = std::views::take(o, 0); + static_assert(is_optional<decltype(tvz)> == usesOptional); + static_assert(std::is_same_v<decltype(*tvz.begin()), T&>); + VERIFY(std::ranges::empty(tvz)); + + std::optional<T> e; + auto tvep = std::views::take(e, 5); + static_assert(is_optional<decltype(tvep)> == usesOptional); + static_assert(std::is_same_v<decltype(*tvep.begin()), T&>); + VERIFY(std::ranges::empty(tvep)); + + auto tvez = std::views::take(e, 0); + static_assert(is_optional<decltype(tvez)> == usesOptional); + static_assert(std::is_same_v<decltype(*tvez.begin()), T&>); + VERIFY(std::ranges::empty(tvez)); +} + +template<bool usesOptional, typename T, typename U = std::remove_cv_t<T>> +constexpr void +test_drop(std::type_identity_t<U> u) +{ + std::optional<T> o(std::in_place, std::forward<U>(u)); + auto dvp = std::views::drop(o, 3); + static_assert(is_optional<decltype(dvp)> == usesOptional); + static_assert(std::is_same_v<decltype(*dvp.begin()), T&>); + VERIFY(std::ranges::empty(dvp)); + + auto dvz = std::views::drop(o, 0); + static_assert(is_optional<decltype(dvz)> == usesOptional); + static_assert(std::is_same_v<decltype(*dvz.begin()), T&>); + VERIFY(!std::ranges::empty(dvz)); + + std::optional<T> e; + auto dvep = std::views::drop(e, 5); + static_assert(is_optional<decltype(dvep)> == usesOptional); + static_assert(std::is_same_v<decltype(*dvep.begin()), T&>); + VERIFY(std::ranges::empty(dvep)); + + auto dvez = std::views::drop(e, 0); + static_assert(is_optional<decltype(dvez)> == usesOptional); + static_assert(std::is_same_v<decltype(*dvez.begin()), T&>); + VERIFY(std::ranges::empty(dvez)); +} + constexpr bool all_tests() @@ -175,6 +289,8 @@ all_tests() test(42); int i = 42; int arr[10]{}; + NonMovable nm; + NonAssignable na; test(&i); test(std::string_view("test")); test(std::vector<int>{1, 2, 3, 4}); @@ -185,6 +301,10 @@ all_tests() test<const int&>(i); test<int(&)[10]>(arr); test<const int(&)[10]>(arr); + test<NonMovable&>(nm); + test<const NonMovable&>(nm); + test<NonAssignable&>(na); + test<const NonAssignable&>(na); test_not_range<void(&)()>(); test_not_range<void(&)(int)>(); test_not_range<int(&)[]>(); @@ -192,6 +312,37 @@ all_tests() range_chain_example(); + test_as_const<false, int>(i); + test_as_const<false, const int>(i); + test_as_const<true, int&>(i); + test_as_const<true, const int&>(i); + test_as_const<false, NonMovable, int>(10); + test_as_const<false, const NonMovable, int>(10); + test_as_const<true, NonMovable&>(nm); + test_as_const<true, const NonMovable&>(nm); + test_as_const<false, NonAssignable>({}); + test_as_const<false, const NonAssignable>({}); + test_as_const<true, NonAssignable&>(na); + test_as_const<true, const NonAssignable&>(na); + +#define TEST_ADAPTOR(name) \ + test_##name<true, int>(i); \ + test_##name<false, const int>(i); \ + test_##name<true, int&>(i); \ + test_##name<true, const int&>(i); \ + test_##name<false, NonMovable, int>(10); \ + test_##name<false, const NonMovable, int>(10); \ + test_##name<true, NonMovable&>(nm); \ + test_##name<true, const NonMovable&>(nm); \ + test_##name<false, NonAssignable>({}); \ + test_##name<false, const NonAssignable>({}); \ + test_##name<true, NonAssignable&>(na); \ + test_##name<true, const NonAssignable&>(na) + + TEST_ADAPTOR(reverse); + TEST_ADAPTOR(take); + TEST_ADAPTOR(drop); +#undef TEST_ADAPTOR return true; }
