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
>