On Sun, 9 Nov 2025 at 00:59, Jonathan Wakely <[email protected]> wrote:
>
> On Fri, 7 Nov 2025 at 16:14, Tomasz Kamiński <[email protected]> wrote:
> >
> > This implements proposed resolution for LWG4308 [1].
> >
> > For T denoting either function type or unbounded array, the optional<T&> no
> > longer exposes iterator, and viable begin/end members. The conditionally
> > provided iterator type, it is now defined in __optional_ref_base
> > base class.
> >
> > Furthermore, range support for optional<T&> is now also guarded by
> > __cpp_lib_optional_range_support.
> >
> > [1] https://cplusplus.github.io/LWG/issue4308
> >
> > PR libstdc++/122396
> >
> > libstdc++-v3/ChangeLog:
> >
> > * include/std/optional (__optional_ref_base): Define.
> > (std::optional<_Tp&>): Inherit from __optional_ref_base<_Tp>.
> > (optional<_Tp&>::iterator): Move to base class.
> > (optional<_Tp&>::begin, optional<_Tp&>::end): Use deduced return
> > type and constrain accordingly.
> > * testsuite/20_util/optional/range.cc: Add test for optional<T&>.
> > ---
> > v2 guards range support for optional<T&> with
> > __cpp_lib_optional_range_support.
>
> OK for trunk.
(without the atomic_ref testcase, obviously)
>
>
> >
> > libstdc++-v3/include/std/optional | 39 +++++++---
> > .../testsuite/20_util/optional/range.cc | 56 +++++++++++----
> > .../testsuite/29_atomics/atomic_ref/bool.cc | 18 ++++-
> > .../29_atomics/atomic_ref/requirements.cc | 71 +++++++++++--------
> > 4 files changed, 131 insertions(+), 53 deletions(-)
> >
> > diff --git a/libstdc++-v3/include/std/optional
> > b/libstdc++-v3/include/std/optional
> > index c4b56e31d58..d191e51ed79 100644
> > --- a/libstdc++-v3/include/std/optional
> > +++ b/libstdc++-v3/include/std/optional
> > @@ -1484,13 +1484,32 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >
> > #if __cpp_lib_optional >= 202506L // C++26
> > template<typename _Tp>
> > - class optional<_Tp&>
> > + class optional<_Tp&>;
> > +
> > + template<typename _Tp>
> > + struct __optional_ref_base
> > + {};
> > +
> > +#ifdef __cpp_lib_optional_range_support // >= C++26
> > + template<typename _Tp>
> > + struct __optional_ref_base<_Tp[]>
> > + {};
> > +
> > + template<typename _Tp>
> > + requires is_object_v<_Tp>
> > + struct __optional_ref_base<_Tp>
> > + {
> > + using iterator = __gnu_cxx::__normal_iterator<_Tp*, optional<_Tp&>>;
> > + };
> > +#endif // __cpp_lib_optional_range_support
> > +
> > + template<typename _Tp>
> > + class optional<_Tp&> : public __optional_ref_base<_Tp>
> > {
> > static_assert(__is_valid_contained_type_for_optional<_Tp&>);
> >
> > public:
> > using value_type = _Tp;
> > - using iterator = __gnu_cxx::__normal_iterator<_Tp*, optional>;
> >
> > // Constructors.
> > constexpr optional() noexcept = default;
> > @@ -1652,16 +1671,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > constexpr void swap(optional& __rhs) noexcept
> > { std::swap(_M_val, __rhs._M_val); }
> >
> > +#ifdef __cpp_lib_optional_range_support // >= C++26
> > // Iterator support.
> > - constexpr iterator begin() const noexcept
> > - {
> > - return iterator(_M_val);
> > - }
> > + constexpr auto begin() const noexcept
> > + requires is_object_v<_Tp> && (!is_unbounded_array_v<_Tp>)
> > + { return __gnu_cxx::__normal_iterator<_Tp*, optional>(_M_val); }
> >
> > - constexpr iterator end() const noexcept
> > - {
> > - return begin() + has_value();
> > - }
> > + constexpr auto end() const noexcept
> > + requires is_object_v<_Tp> && (!is_unbounded_array_v<_Tp>)
> > + { return begin() + has_value(); }
> > +#endif // __cpp_lib_optional_range_support
> >
> > // Observers.
> > constexpr _Tp* operator->() const noexcept
> > diff --git a/libstdc++-v3/testsuite/20_util/optional/range.cc
> > b/libstdc++-v3/testsuite/20_util/optional/range.cc
> > index e77dc21e22b..1cb3eb6ceff 100644
> > --- a/libstdc++-v3/testsuite/20_util/optional/range.cc
> > +++ b/libstdc++-v3/testsuite/20_util/optional/range.cc
> > @@ -10,18 +10,18 @@
> >
> > #include <testsuite_hooks.h>
> >
> > -template<typename O>
> > +template<typename T>
> > constexpr
> > void
> > test_range_concepts()
> > {
> > + using O = std::optional<T>;
> > static_assert(std::ranges::contiguous_range<O>);
> > static_assert(std::ranges::sized_range<O>);
> > static_assert(std::ranges::common_range<O>);
> > static_assert(!std::ranges::borrowed_range<O>);
> >
> > // an optional<const T> is not assignable, and therefore does not
> > satisfy ranges::view
> > - using T = typename O::value_type;
> > 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);
> > @@ -39,7 +39,14 @@ test_iterator_concepts()
> > static_assert(std::is_same_v<std::iter_value_t<iterator>,
> > std::remove_cv_t<T>>);
> > static_assert(std::is_same_v<typename
> > std::iterator_traits<iterator>::reference, T&>);
> > static_assert(std::is_same_v<std::iter_reference_t<iterator>, T&>);
> > +}
> >
> > +template<typename O>
> > +constexpr
> > +void
> > +test_const_iterator_concepts()
> > +{
> > + using T = typename O::value_type;
> > using const_iterator = typename O::const_iterator;
> > static_assert(std::contiguous_iterator<const_iterator>);
> > static_assert(std::is_same_v<typename
> > std::iterator_traits<const_iterator>::value_type, std::remove_cv_t<T>>);
> > @@ -48,11 +55,12 @@ test_iterator_concepts()
> > static_assert(std::is_same_v<std::iter_reference_t<const_iterator>,
> > const T&>);
> > }
> >
> > -template<typename O>
> > +template<typename T>
> > constexpr
> > void
> > test_empty()
> > {
> > + using O = std::optional<T>;
> > O empty;
> > VERIFY(!empty);
> > VERIFY(empty.begin() == empty.end());
> > @@ -69,14 +77,18 @@ test_empty()
> > VERIFY(count == 0);
> > }
> >
> > -template<typename O, typename T>
> > +template<typename T>
> > constexpr
> > void
> > test_non_empty(const T& value)
> > {
> > - O non_empty = std::make_optional(value);
> > + using O = std::optional<T>;
> > + using V = typename O::value_type;
> > + O non_empty(std::in_place, value);
> > VERIFY(non_empty);
> > - VERIFY(*non_empty == value);
> > + if constexpr (!std::is_array_v<V>)
> > + VERIFY(*non_empty == value);
> > +
> > VERIFY(non_empty.begin() != non_empty.end());
> > VERIFY(non_empty.begin() < non_empty.end());
> > VERIFY(std::as_const(non_empty).begin() !=
> > std::as_const(non_empty).end());
> > @@ -92,11 +104,11 @@ test_non_empty(const T& value)
> > ++count;
> > VERIFY(count == 1);
> >
> > - if constexpr (!std::is_const_v<typename O::value_type>) {
> > + if constexpr (!std::is_const_v<V> && !std::is_array_v<V>) {
> > for (auto& x : non_empty)
> > - x = T{};
> > + x = V{};
> > VERIFY(non_empty);
> > - VERIFY(*non_empty == T{});
> > + VERIFY(*non_empty == V{});
> > }
> > }
> >
> > @@ -106,10 +118,12 @@ void
> > test(const T& value)
> > {
> > using O = std::optional<T>;
> > - test_range_concepts<O>();
> > + test_range_concepts<T>();
> > test_iterator_concepts<O>();
> > - test_empty<O>();
> > - test_non_empty<O>(value);
> > + if constexpr (!std::is_reference_v<T>)
> > + test_const_iterator_concepts<O>();
> > + test_empty<T>();
> > + test_non_empty<T>(value);
> > static_assert(!std::formattable<O, char>);
> > static_assert(!std::formattable<O, wchar_t>);
> > static_assert(std::format_kind<O> == std::range_format::disabled);
> > @@ -142,18 +156,36 @@ range_chain_example() // from P3168
> > VERIFY(ok);
> > }
> >
> > +template<typename T>
> > +constexpr void test_not_range()
> > +{
> > + static_assert(!requires { typename std::optional<T>::iterator; });
> > + static_assert(!requires(std::optional<T> o) { o.begin(); });
> > + static_assert(!requires(std::optional<T> o) { o.end(); });
> > +};
> > +
> > constexpr
> > bool
> > all_tests()
> > {
> > test(42);
> > int i = 42;
> > + int arr[10]{};
> > test(&i);
> > test(std::string_view("test"));
> > test(std::vector<int>{1, 2, 3, 4});
> > test(std::optional<int>(42));
> > test<const int>(42);
> >
> > + test<int&>(i);
> > + test<const int&>(i);
> > + test<int(&)[10]>(arr);
> > + test<const int(&)[10]>(arr);
> > + test_not_range<void(&)()>();
> > + test_not_range<void(&)(int)>();
> > + test_not_range<int(&)[]>();
> > + test_not_range<const int(&)[]>();
> > +
> > range_chain_example();
> >
> > return true;
> > diff --git a/libstdc++-v3/testsuite/29_atomics/atomic_ref/bool.cc
> > b/libstdc++-v3/testsuite/29_atomics/atomic_ref/bool.cc
> > index c73319010ee..61256a76117 100644
> > --- a/libstdc++-v3/testsuite/29_atomics/atomic_ref/bool.cc
> > +++ b/libstdc++-v3/testsuite/29_atomics/atomic_ref/bool.cc
> > @@ -66,8 +66,6 @@ test02()
> > std::atomic_ref<bool> a0(b);
> > std::atomic_ref<bool> a1(b);
> > std::atomic_ref<const bool> a1c(b);
> > - std::atomic_ref<volatile bool> a1v(b);
> > - std::atomic_ref<const volatile bool> a1cv(b);
> > std::atomic_ref<bool> a2(a0);
> > b = true;
> > VERIFY( a1.load() );
> > @@ -77,9 +75,25 @@ test02()
> > VERIFY( a2.load() );
> > }
> >
> > +template<typename T = bool>
> > +void
> > +test03()
> > +{
> > + if constexpr (std::atomic_ref<T>::is_always_lock_free)
> > + {
> > + bool b = false;
> > + std::atomic_ref<volatile T> a1v(b);
> > + std::atomic_ref<const volatile T> a1cv(b);
> > + b = true;
> > + VERIFY( a1v.load() );
> > + VERIFY( a1cv.load() );
> > + }
> > +}
> > +
> > int
> > main()
> > {
> > test01();
> > test02();
> > + test03();
> > }
> > diff --git a/libstdc++-v3/testsuite/29_atomics/atomic_ref/requirements.cc
> > b/libstdc++-v3/testsuite/29_atomics/atomic_ref/requirements.cc
> > index 8617661f8e1..930138edb22 100644
> > --- a/libstdc++-v3/testsuite/29_atomics/atomic_ref/requirements.cc
> > +++ b/libstdc++-v3/testsuite/29_atomics/atomic_ref/requirements.cc
> > @@ -21,17 +21,21 @@
> > #include <type_traits>
> >
> > template <class T>
> > + requires (!std::is_volatile_v<T>) ||
> > std::atomic_ref<std::remove_cv_t<T>>::is_always_lock_free
> > void
> > test_generic()
> > {
> > using A = std::atomic_ref<T>;
> > - static_assert( std::is_standard_layout_v<A> );
> > - static_assert( std::is_nothrow_copy_constructible_v<A> );
> > - static_assert( std::is_trivially_destructible_v<A> );
> > - static_assert( std::is_same_v<typename A::value_type,
> > std::remove_cv_t<T>> );
> > - static_assert( !requires { typename A::difference_type; } );
> > - static_assert( !std::is_copy_assignable_v<A> );
> > - static_assert( !std::is_move_assignable_v<A> );
> > + if constexpr (!std::is_volatile_v<T> || A::is_always_lock_free)
> > + {
> > + static_assert( std::is_standard_layout_v<A> );
> > + static_assert( std::is_nothrow_copy_constructible_v<A> );
> > + static_assert( std::is_trivially_destructible_v<A> );
> > + static_assert( std::is_same_v<typename A::value_type,
> > std::remove_cv_t<T>> );
> > + static_assert( !requires { typename A::difference_type; } );
> > + static_assert( !std::is_copy_assignable_v<A> );
> > + static_assert( !std::is_move_assignable_v<A> );
> > + }
> > }
> >
> > template <class T>
> > @@ -39,13 +43,16 @@ void
> > test_integral()
> > {
> > using A = std::atomic_ref<T>;
> > - static_assert( std::is_standard_layout_v<A> );
> > - static_assert( std::is_nothrow_copy_constructible_v<A> );
> > - static_assert( std::is_trivially_destructible_v<A> );
> > - static_assert( std::is_same_v<typename A::value_type,
> > std::remove_cv_t<T>> );
> > - static_assert( std::is_same_v<typename A::difference_type, typename
> > A::value_type> );
> > - static_assert( !std::is_copy_assignable_v<A> );
> > - static_assert( !std::is_move_assignable_v<A> );
> > + if constexpr (!std::is_volatile_v<T> || A::is_always_lock_free)
> > + {
> > + static_assert( std::is_standard_layout_v<A> );
> > + static_assert( std::is_nothrow_copy_constructible_v<A> );
> > + static_assert( std::is_trivially_destructible_v<A> );
> > + static_assert( std::is_same_v<typename A::value_type,
> > std::remove_cv_t<T>> );
> > + static_assert( std::is_same_v<typename A::difference_type, typename
> > A::value_type> );
> > + static_assert( !std::is_copy_assignable_v<A> );
> > + static_assert( !std::is_move_assignable_v<A> );
> > + }
> > }
> >
> > template <class T>
> > @@ -53,13 +60,16 @@ void
> > test_floating_point()
> > {
> > using A = std::atomic_ref<T>;
> > - static_assert( std::is_standard_layout_v<A> );
> > - static_assert( std::is_nothrow_copy_constructible_v<A> );
> > - static_assert( std::is_trivially_destructible_v<A> );
> > - static_assert( std::is_same_v<typename A::value_type,
> > std::remove_cv_t<T>> );
> > - static_assert( std::is_same_v<typename A::difference_type, typename
> > A::value_type> );
> > - static_assert( !std::is_copy_assignable_v<A> );
> > - static_assert( !std::is_move_assignable_v<A> );
> > + if constexpr (!std::is_volatile_v<T> || A::is_always_lock_free)
> > + {
> > + static_assert( std::is_standard_layout_v<A> );
> > + static_assert( std::is_nothrow_copy_constructible_v<A> );
> > + static_assert( std::is_trivially_destructible_v<A> );
> > + static_assert( std::is_same_v<typename A::value_type,
> > std::remove_cv_t<T>> );
> > + static_assert( std::is_same_v<typename A::difference_type, typename
> > A::value_type> );
> > + static_assert( !std::is_copy_assignable_v<A> );
> > + static_assert( !std::is_move_assignable_v<A> );
> > + }
> > }
> >
> > template <class T>
> > @@ -67,14 +77,17 @@ void
> > test_pointer()
> > {
> > using A = std::atomic_ref<T>;
> > - static_assert( std::is_standard_layout_v<A> );
> > - static_assert( std::is_nothrow_copy_constructible_v<A> );
> > - static_assert( std::is_trivially_destructible_v<A> );
> > - static_assert( std::is_same_v<typename A::value_type,
> > std::remove_cv_t<T>> );
> > - static_assert( std::is_same_v<typename A::difference_type,
> > std::ptrdiff_t> );
> > - static_assert( std::is_nothrow_copy_constructible_v<A> );
> > - static_assert( !std::is_copy_assignable_v<A> );
> > - static_assert( !std::is_move_assignable_v<A> );
> > + if constexpr (!std::is_volatile_v<T> || A::is_always_lock_free)
> > + {
> > + static_assert( std::is_standard_layout_v<A> );
> > + static_assert( std::is_nothrow_copy_constructible_v<A> );
> > + static_assert( std::is_trivially_destructible_v<A> );
> > + static_assert( std::is_same_v<typename A::value_type,
> > std::remove_cv_t<T>> );
> > + static_assert( std::is_same_v<typename A::difference_type,
> > std::ptrdiff_t> );
> > + static_assert( std::is_nothrow_copy_constructible_v<A> );
> > + static_assert( !std::is_copy_assignable_v<A> );
> > + static_assert( !std::is_move_assignable_v<A> );
> > + }
> > }
> >
> > int
> > --
> > 2.51.0
> >