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.


>
>  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
>

Reply via email to