On Thu, 13 Nov 2025 at 11:50, Tomasz Kaminski <[email protected]> wrote:
>
>
>
> 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,
Oh, I missed that change in the updated patch, sorry!
> 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.
Yes, but irrelevant now :-)
I've reviewed the v3 patch again more carefully. OK for trunk, thanks.
>>
>>
>> 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
>> >
>>