On Thu, Nov 13, 2025 at 12:43 PM Jonathan Wakely <[email protected]> wrote:
> On Wed, 12 Nov 2025 at 11:38, Tomasz Kamiński <[email protected]> wrote: > > > > 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_v): Define. > > * include/std/ranges (_Take::operator(), _Drop::operator()) > > Please mention __is_optional_and_view here as well. > I believe I dropped it completely in actual patch here: https://gcc.gnu.org/pipermail/libstdc++/2025-November/064296.html, in favor of checking is_optional_v<_Tp> && view<_Tp>. I initially wanted to optimize the checking by replacing view<_Tp> with !is_const_v<template-arg>, but Hewill pointed out that type may just not be move-constructible or move-assingable in first place. > > But I wonder if __is_viewable_optional would be a better name, for > symmetry with the std::ranges::viewable_range concept. > That would be a better name, if we would still have the variable. > > What do you think? > > > > (_Reverse::operator()): Handle optional<T> that are view. > > (_AsConst::operator()): Handle optional<T&>. > > * testsuite/20_util/optional/range.cc: New tests. > > --- > > The changes got approved for C++26 so submitting this as proper patch. > > v3 expands existing test cases, to cover references to NonMovable and > > NonAssingable types. Use TEST_ADAPTOR macro to generate test cases > > for adaptors (except ranges::as_const). > > > > Tested on x86_64-linux locally. *optional* test also tested with all > > supported standard modes. OK for trunk? > > > > libstdc++-v3/include/std/optional | 8 +- > > libstdc++-v3/include/std/ranges | 16 ++ > > .../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 75a9531ccd5..41c04b10720 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 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<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 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 <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; > > } > > > > -- > > 2.51.0 > > > >
