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_iter_base base class.
[1] https://cplusplus.github.io/LWG/issue4308 PR libstdc++/122396 libstdc++-v3/ChangeLog: * include/std/optional (__optional_ref_iter_base): Define. (std::optional<_Tp&>): Inherit from __optional_ref_iter_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&>. --- libstdc++-v3/include/std/optional | 35 ++++++++---- .../testsuite/20_util/optional/range.cc | 56 +++++++++++++++---- 2 files changed, 69 insertions(+), 22 deletions(-) diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional index c4b56e31d58..fad5f57a94c 100644 --- a/libstdc++-v3/include/std/optional +++ b/libstdc++-v3/include/std/optional @@ -1484,13 +1484,30 @@ _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_iter_base + {}; + + template<typename _Tp> + struct __optional_ref_iter_base<_Tp[]> + {}; + + template<typename _Tp> + requires is_object_v<_Tp> + struct __optional_ref_iter_base<_Tp> + { + using iterator = __gnu_cxx::__normal_iterator<_Tp*, optional<_Tp&>>; + }; + + template<typename _Tp> + class optional<_Tp&> : public __optional_ref_iter_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; @@ -1653,15 +1670,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { std::swap(_M_val, __rhs._M_val); } // 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(); } // 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; -- 2.51.0
