On Fri, Nov 7, 2025 at 5:15 PM 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. > > 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 > This are change from other PR I was working on, please ignore. > 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 > >
