From: Tomasz Kamiński Date: Fri, 7 Nov 2025 17:17:56 +0000 (+0100) Subject: libstdc++: Optimize handling of optional for views: take, drop, reverse and as_const. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=35f05d04f305dc51364469d20879dbea8810b0db;p=thirdparty%2Fgcc.git 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 that is a view: * views::reverse(opt), views::take(opt, n), and views::drop(opt, n) returns optional. * views::as_const(opt), optional is converted into optional. optional 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 that are view. (_AsConst::operator()): Handle optional. * testsuite/20_util/optional/range.cc: New tests. Reviewed-by: Jonathan Wakely Signed-off-by: Tomasz Kamiński --- diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional index 75a9531ccd5..41c04b10720 100644 --- a/libstdc++-v3/include/std/optional +++ b/libstdc++-v3/include/std/optional @@ -1486,6 +1486,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template class optional<_Tp&>; + template + constexpr bool __is_optional_ref_v = false; + + template + constexpr bool __is_optional_ref_v> = true; + template struct __optional_ref_base {}; @@ -2187,7 +2193,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION constexpr bool ranges::enable_borrowed_range> = true; #endif - + template inline constexpr range_format format_kind> = range_format::disabled; diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges index 158692d92a7..ae57b9a0809 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; +#if __cpp_lib_optional >= 202506L && __cpp_lib_optional_range_support // >= C++26 + else if constexpr (__is_optional_ref_v<_Tp>) + return optional(__r); +#endif else if constexpr (std::__detail::__is_span<_Tp>) return span(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 981969cb614..4238c9cf28e 100644 --- a/libstdc++-v3/testsuite/20_util/optional/range.cc +++ b/libstdc++-v3/testsuite/20_util/optional/range.cc @@ -10,6 +10,26 @@ #include +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 constexpr void @@ -24,10 +44,11 @@ test_range_concepts() constexpr bool is_ref_opt = std::is_reference_v; static_assert(std::ranges::borrowed_range == is_ref_opt); - // an optional is not assignable, and therefore does not satisfy ranges::view - constexpr bool is_const_opt = std::is_const_v; - static_assert(std::ranges::view == !is_const_opt); - static_assert(std::ranges::viewable_range == !is_const_opt); + // for any T (including const U) such that optional is not assignable, + // it does not satisfy ranges::view + constexpr bool is_opt_view = std::is_reference_v || std::movable; + static_assert(std::ranges::view == is_opt_view); + static_assert(std::ranges::viewable_range == is_opt_view); } @@ -108,7 +129,7 @@ test_non_empty(const T& value) ++count; VERIFY(count == 1); - if constexpr (!std::is_const_v && !std::is_array_v) { + if constexpr (std::is_move_assignable_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 o) { o.end(); }); }; +template +constexpr bool is_optional = false; + +template +constexpr bool is_optional> = true; + +template> +constexpr void test_as_const(std::type_identity_t u) +{ + std::optional o(std::in_place, std::forward(u)); + auto cv = std::views::as_const(o); + static_assert(is_optional == usesOptional); + static_assert(std::is_same_v&>); + VERIFY(!std::ranges::empty(cv)); + + std::optional e; + auto cve = std::views::as_const(e); + static_assert(is_optional == usesOptional); + static_assert(std::is_same_v&>); + VERIFY(std::ranges::empty(cve)); +} + +template> +constexpr void +test_reverse(std::type_identity_t u) +{ + std::optional o(std::in_place, std::forward(u)); + auto rv = std::views::reverse(o); + static_assert(is_optional == usesOptional); + static_assert(std::is_same_v); + VERIFY(!std::ranges::empty(rv)); + + std::optional e; + auto rve = std::views::reverse(e); + static_assert(is_optional == usesOptional); + static_assert(std::is_same_v); + VERIFY(std::ranges::empty(rve)); +} + +template> +constexpr void +test_take(std::type_identity_t u) +{ + std::optional o(std::in_place, std::forward(u)); + auto tvp = std::views::take(o, 3); + static_assert(is_optional == usesOptional); + static_assert(std::is_same_v); + VERIFY(!std::ranges::empty(tvp)); + + auto tvz = std::views::take(o, 0); + static_assert(is_optional == usesOptional); + static_assert(std::is_same_v); + VERIFY(std::ranges::empty(tvz)); + + std::optional e; + auto tvep = std::views::take(e, 5); + static_assert(is_optional == usesOptional); + static_assert(std::is_same_v); + VERIFY(std::ranges::empty(tvep)); + + auto tvez = std::views::take(e, 0); + static_assert(is_optional == usesOptional); + static_assert(std::is_same_v); + VERIFY(std::ranges::empty(tvez)); +} + +template> +constexpr void +test_drop(std::type_identity_t u) +{ + std::optional o(std::in_place, std::forward(u)); + auto dvp = std::views::drop(o, 3); + static_assert(is_optional == usesOptional); + static_assert(std::is_same_v); + VERIFY(std::ranges::empty(dvp)); + + auto dvz = std::views::drop(o, 0); + static_assert(is_optional == usesOptional); + static_assert(std::is_same_v); + VERIFY(!std::ranges::empty(dvz)); + + std::optional e; + auto dvep = std::views::drop(e, 5); + static_assert(is_optional == usesOptional); + static_assert(std::is_same_v); + VERIFY(std::ranges::empty(dvep)); + + auto dvez = std::views::drop(e, 0); + static_assert(is_optional == usesOptional); + static_assert(std::is_same_v); + 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{1, 2, 3, 4}); @@ -185,6 +301,10 @@ all_tests() test(i); test(arr); test(arr); + test(nm); + test(nm); + test(na); + test(na); test_not_range(); test_not_range(); test_not_range(); @@ -192,6 +312,37 @@ all_tests() range_chain_example(); + test_as_const(i); + test_as_const(i); + test_as_const(i); + test_as_const(i); + test_as_const(10); + test_as_const(10); + test_as_const(nm); + test_as_const(nm); + test_as_const({}); + test_as_const({}); + test_as_const(na); + test_as_const(na); + +#define TEST_ADAPTOR(name) \ + test_##name(i); \ + test_##name(i); \ + test_##name(i); \ + test_##name(i); \ + test_##name(10); \ + test_##name(10); \ + test_##name(nm); \ + test_##name(nm); \ + test_##name({}); \ + test_##name({}); \ + test_##name(na); \ + test_##name(na) + + TEST_ADAPTOR(reverse); + TEST_ADAPTOR(take); + TEST_ADAPTOR(drop); +#undef TEST_ADAPTOR return true; }