[libc++] Implement P3168R2: Give optional range support (#149441)

Resolves #105430

- Implement all required pieces of P3168R2
- Leverage existing `wrap_iter` and `bounded_iter` classes to implement
the `optional` regular and hardened iterator type, respectively
- Update documentation to match
This commit is contained in:
William Tran-Viet 2025-08-18 11:04:45 -04:00 committed by GitHub
parent 4ab14685a0
commit 1c51886920
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 361 additions and 27 deletions

View File

@ -5,5 +5,6 @@ set(_defines
_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR
_LIBCPP_ABI_BOUNDED_UNIQUE_PTR
_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY
_LIBCPP_ABI_BOUNDED_ITERATORS_IN_OPTIONAL
)
set(LIBCXX_ABI_DEFINES "${_defines}" CACHE STRING "")

View File

@ -480,7 +480,7 @@ Status
---------------------------------------------------------- -----------------
``__cpp_lib_not_fn`` ``202306L``
---------------------------------------------------------- -----------------
``__cpp_lib_optional_range_support`` *unimplemented*
``__cpp_lib_optional_range_support`` ``202406L``
---------------------------------------------------------- -----------------
``__cpp_lib_out_ptr`` ``202311L``
---------------------------------------------------------- -----------------

View File

@ -39,6 +39,7 @@ Implemented Papers
------------------
- P2321R2: ``zip`` (`Github <https://github.com/llvm/llvm-project/issues/105169>`__) (The paper is partially implemented. ``zip_transform_view`` is implemented in this release)
- P3168R2: Give ``std::optional`` Range Support (`Github <https://github.com/llvm/llvm-project/issues/105430>`__)
Improvements and New Features
-----------------------------

View File

@ -66,7 +66,7 @@
"`P2747R2 <https://wg21.link/P2747R2>`__","``constexpr`` placement new","2024-06 (St. Louis)","|Complete|","20",""
"`P2997R1 <https://wg21.link/P2997R1>`__","Removing the common reference requirement from the indirectly invocable concepts","2024-06 (St. Louis)","|Complete|","19","Implemented as a DR against C++20. (MSVC STL and libstdc++ will do the same.)"
"`P2389R2 <https://wg21.link/P2389R2>`__","``dextents`` Index Type Parameter","2024-06 (St. Louis)","|Complete|","19",""
"`P3168R2 <https://wg21.link/P3168R2>`__","Give ``std::optional`` Range Support","2024-06 (St. Louis)","","",""
"`P3168R2 <https://wg21.link/P3168R2>`__","Give ``std::optional`` Range Support","2024-06 (St. Louis)","|Complete|","22",""
"`P3217R0 <https://wg21.link/P3217R0>`__","Adjoints to 'Enabling list-initialization for algorithms': find_last","2024-06 (St. Louis)","","",""
"`P2985R0 <https://wg21.link/P2985R0>`__","A type trait for detecting virtual base classes","2024-06 (St. Louis)","|Complete|","20",""
"`P0843R14 <https://wg21.link/P0843R14>`__","``inplace_vector``","2024-06 (St. Louis)","","",""

1 Paper # Paper Name Meeting Status First released version Notes
66 `P2747R2 <https://wg21.link/P2747R2>`__ ``constexpr`` placement new 2024-06 (St. Louis) |Complete| 20
67 `P2997R1 <https://wg21.link/P2997R1>`__ Removing the common reference requirement from the indirectly invocable concepts 2024-06 (St. Louis) |Complete| 19 Implemented as a DR against C++20. (MSVC STL and libstdc++ will do the same.)
68 `P2389R2 <https://wg21.link/P2389R2>`__ ``dextents`` Index Type Parameter 2024-06 (St. Louis) |Complete| 19
69 `P3168R2 <https://wg21.link/P3168R2>`__ Give ``std::optional`` Range Support 2024-06 (St. Louis) |Complete| 22
70 `P3217R0 <https://wg21.link/P3217R0>`__ Adjoints to 'Enabling list-initialization for algorithms': find_last 2024-06 (St. Louis)
71 `P2985R0 <https://wg21.link/P2985R0>`__ A type trait for detecting virtual base classes 2024-06 (St. Louis) |Complete| 20
72 `P0843R14 <https://wg21.link/P0843R14>`__ ``inplace_vector`` 2024-06 (St. Louis)

View File

@ -117,6 +117,8 @@ private:
friend class span;
template <class _Tp, size_t _Size>
friend struct array;
template <class _Tp>
friend class optional;
};
template <class _Iter1>

View File

@ -20,6 +20,11 @@ namespace std {
template <class T>
class optional;
template<class T>
constexpr bool ranges::enable_view<optional<T>> = true;
template<class T>
constexpr auto format_kind<optional<T>> = range_format::disabled;
template<class T>
concept is-derived-from-optional = requires(const T& t) { // exposition only
[]<class U>(const optional<U>&){ }(t);
@ -102,6 +107,8 @@ namespace std {
class optional {
public:
using value_type = T;
using iterator = implementation-defined; // see [optional.iterators]
using const_iterator = implementation-defined; // see [optional.iterators]
// [optional.ctor], constructors
constexpr optional() noexcept;
@ -135,6 +142,12 @@ namespace std {
// [optional.swap], swap
void swap(optional &) noexcept(see below ); // constexpr in C++20
// [optional.iterators], iterator support
constexpr iterator begin() noexcept;
constexpr const_iterator begin() const noexcept;
constexpr iterator end() noexcept;
constexpr const_iterator end() const noexcept;
// [optional.observe], observers
constexpr T const *operator->() const noexcept;
constexpr T *operator->() noexcept;
@ -186,13 +199,18 @@ namespace std {
# include <__compare/three_way_comparable.h>
# include <__concepts/invocable.h>
# include <__config>
# include <__cstddef/ptrdiff_t.h>
# include <__exception/exception.h>
# include <__format/range_format.h>
# include <__functional/hash.h>
# include <__functional/invoke.h>
# include <__functional/unary_function.h>
# include <__fwd/functional.h>
# include <__iterator/bounded_iter.h>
# include <__iterator/wrap_iter.h>
# include <__memory/addressof.h>
# include <__memory/construct_at.h>
# include <__ranges/enable_view.h>
# include <__tuple/sfinae_helpers.h>
# include <__type_traits/add_pointer.h>
# include <__type_traits/conditional.h>
@ -207,6 +225,7 @@ namespace std {
# include <__type_traits/is_convertible.h>
# include <__type_traits/is_core_convertible.h>
# include <__type_traits/is_destructible.h>
# include <__type_traits/is_function.h>
# include <__type_traits/is_nothrow_assignable.h>
# include <__type_traits/is_nothrow_constructible.h>
# include <__type_traits/is_object.h>
@ -219,6 +238,7 @@ namespace std {
# include <__type_traits/is_trivially_constructible.h>
# include <__type_traits/is_trivially_destructible.h>
# include <__type_traits/is_trivially_relocatable.h>
# include <__type_traits/is_unbounded_array.h>
# include <__type_traits/negation.h>
# include <__type_traits/remove_const.h>
# include <__type_traits/remove_cv.h>
@ -567,6 +587,14 @@ using __optional_sfinae_assign_base_t _LIBCPP_NODEBUG =
template <class _Tp>
class optional;
# if _LIBCPP_STD_VER >= 26
template <class _Tp>
constexpr bool ranges::enable_view<optional<_Tp>> = true;
template <class _Tp>
constexpr range_format format_kind<optional<_Tp>> = range_format::disabled;
# endif
# if _LIBCPP_STD_VER >= 20
template <class _Tp>
@ -586,9 +614,21 @@ class _LIBCPP_DECLSPEC_EMPTY_BASES optional
private __optional_sfinae_assign_base_t<_Tp> {
using __base _LIBCPP_NODEBUG = __optional_move_assign_base<_Tp>;
using __pointer _LIBCPP_NODEBUG = std::add_pointer_t<_Tp>;
using __const_pointer _LIBCPP_NODEBUG = std::add_pointer_t<const _Tp>;
public:
using value_type = _Tp;
# if _LIBCPP_STD_VER >= 26
# ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_OPTIONAL
using iterator = __bounded_iter<__wrap_iter<__pointer>>;
using const_iterator = __bounded_iter<__wrap_iter<__const_pointer>>;
# else
using iterator = __wrap_iter<__pointer>;
using const_iterator = __wrap_iter<__const_pointer>;
# endif
# endif
using __trivially_relocatable _LIBCPP_NODEBUG =
conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, optional, void>;
using __replaceable _LIBCPP_NODEBUG = conditional_t<__is_replaceable_v<_Tp>, optional, void>;
@ -792,6 +832,34 @@ public:
}
}
# if _LIBCPP_STD_VER >= 26
// [optional.iterators], iterator support
_LIBCPP_HIDE_FROM_ABI constexpr iterator begin() noexcept {
# ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_OPTIONAL
return std::__make_bounded_iter(
std::__wrap_iter<__pointer>(std::addressof(this->__get())),
std::__wrap_iter<__pointer>(std::addressof(this->__get())),
std::__wrap_iter<__pointer>(std::addressof(this->__get()) + (this->has_value() ? 1 : 0)));
# else
return iterator(std::addressof(this->__get()));
# endif
}
_LIBCPP_HIDE_FROM_ABI constexpr const_iterator begin() const noexcept {
# ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_OPTIONAL
return std::__make_bounded_iter(
std::__wrap_iter<__const_pointer>(std::addressof(this->__get())),
std::__wrap_iter<__const_pointer>(std::addressof(this->__get())),
std::__wrap_iter<__const_pointer>(std::addressof(this->__get()) + (this->has_value() ? 1 : 0)));
# else
return const_iterator(std::addressof(this->__get()));
# endif
}
_LIBCPP_HIDE_FROM_ABI constexpr iterator end() noexcept { return begin() + (this->has_value() ? 1 : 0); }
_LIBCPP_HIDE_FROM_ABI constexpr const_iterator end() const noexcept { return begin() + (this->has_value() ? 1 : 0); }
# endif
_LIBCPP_HIDE_FROM_ABI constexpr add_pointer_t<value_type const> operator->() const noexcept {
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator-> called on a disengaged value");
return std::addressof(this->__get());

View File

@ -585,7 +585,7 @@ __cpp_lib_void_t 201411L <type_traits>
# define __cpp_lib_mdspan 202406L
# undef __cpp_lib_not_fn
# define __cpp_lib_not_fn 202306L
// # define __cpp_lib_optional_range_support 202406L
# define __cpp_lib_optional_range_support 202406L
# undef __cpp_lib_out_ptr
# define __cpp_lib_out_ptr 202311L
// # define __cpp_lib_philox_engine 202406L

View File

@ -10,7 +10,12 @@
export namespace std {
// [optional.optional], class template optional
using std::optional;
#if _LIBCPP_STD_VER >= 26
// [optional.iterators], iterator support
namespace ranges {
using std::ranges::enable_view;
}
#endif
// [optional.nullopt], no-value state indicator
using std::nullopt;
using std::nullopt_t;
@ -18,6 +23,10 @@ export namespace std {
// [optional.bad.access], class bad_optional_access
using std::bad_optional_access;
#if _LIBCPP_STD_VER >= 26
using std::format_kind;
#endif
// [optional.relops], relational operators
using std::operator==;
using std::operator!=;

View File

@ -0,0 +1,30 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// REQUIRES: std-at-least-c++26
// <optional>
// template <class T> class optional::iterator;
// template <class T> class optional::const_iterator;
#include <optional>
template <typename T>
concept has_iterator_aliases = requires {
typename T::iterator;
typename T::const_iterator;
};
static_assert(has_iterator_aliases<std::optional<int>>);
static_assert(has_iterator_aliases<std::optional<const int>>);
// TODO: Uncomment these once P2988R12 is implemented, as they would be testing optional<T&>
// static_assert(!has_iterator_aliases<std::optional<int (&)[]>>);
// static_assert(!has_iterator_aliases<std::optional<void (&)(int, char)>>);

View File

@ -146,17 +146,11 @@
# error "__cpp_lib_optional should have the value 202110L in c++26"
# endif
# if !defined(_LIBCPP_VERSION)
# ifndef __cpp_lib_optional_range_support
# error "__cpp_lib_optional_range_support should be defined in c++26"
# endif
# if __cpp_lib_optional_range_support != 202406L
# error "__cpp_lib_optional_range_support should have the value 202406L in c++26"
# endif
# else
# ifdef __cpp_lib_optional_range_support
# error "__cpp_lib_optional_range_support should not be defined because it is unimplemented in libc++!"
# endif
# ifndef __cpp_lib_optional_range_support
# error "__cpp_lib_optional_range_support should be defined in c++26"
# endif
# if __cpp_lib_optional_range_support != 202406L
# error "__cpp_lib_optional_range_support should have the value 202406L in c++26"
# endif
#endif // TEST_STD_VER > 23

View File

@ -7437,17 +7437,11 @@
# error "__cpp_lib_optional should have the value 202110L in c++26"
# endif
# if !defined(_LIBCPP_VERSION)
# ifndef __cpp_lib_optional_range_support
# error "__cpp_lib_optional_range_support should be defined in c++26"
# endif
# if __cpp_lib_optional_range_support != 202406L
# error "__cpp_lib_optional_range_support should have the value 202406L in c++26"
# endif
# else
# ifdef __cpp_lib_optional_range_support
# error "__cpp_lib_optional_range_support should not be defined because it is unimplemented in libc++!"
# endif
# ifndef __cpp_lib_optional_range_support
# error "__cpp_lib_optional_range_support should be defined in c++26"
# endif
# if __cpp_lib_optional_range_support != 202406L
# error "__cpp_lib_optional_range_support should have the value 202406L in c++26"
# endif
# ifndef __cpp_lib_out_ptr

View File

@ -0,0 +1,64 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// REQUIRES: std-at-least-c++26
// <optional>
// constexpr iterator optional::begin() noexcept;
// constexpr const_iterator optional::begin() const noexcept;
#include <cassert>
#include <iterator>
#include <optional>
#include <type_traits>
#include <utility>
template <typename T>
constexpr bool test() {
std::optional<T> opt{T{}};
{ // begin() is marked noexcept
static_assert(noexcept(opt.begin()));
static_assert(noexcept(std::as_const(opt).begin()));
}
{ // Dereferencing an iterator at the beginning == indexing the 0th element, and that calling begin() again return the same iterator.
auto iter1 = opt.begin();
auto iter2 = std::as_const(opt).begin();
assert(*iter1 == iter1[0]);
assert(*iter2 == iter2[0]);
assert(iter1 == opt.begin());
assert(iter2 == std::as_const(opt).begin());
}
{ // Calling begin() multiple times on a disengaged optional returns the same iterator.
std::optional<T> disengaged{std::nullopt};
auto iter1 = disengaged.begin();
auto iter2 = std::as_const(disengaged).begin();
assert(iter1 == disengaged.begin());
assert(iter2 == std::as_const(disengaged).begin());
}
return true;
}
constexpr bool tests() {
assert(test<int>());
assert(test<char>());
assert(test<const int>());
assert(test<const char>());
return true;
}
int main(int, char**) {
assert(tests());
static_assert(tests());
return 0;
}

View File

@ -0,0 +1,74 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// REQUIRES: std-at-least-c++26
// <optional>
// constexpr iterator optional::end() noexcept;
// constexpr const_iterator optional::end() const noexcept;
#include <cassert>
#include <iterator>
#include <optional>
#include <ranges>
#include <utility>
template <typename T>
constexpr bool test() {
std::optional<T> disengaged{std::nullopt};
{ // end() is marked noexcept
static_assert(noexcept(disengaged.end()));
static_assert(noexcept(std::as_const(disengaged).end()));
}
{ // end() == begin() and end() == end() if the optional is disengaged
auto it = disengaged.end();
auto it2 = std::as_const(disengaged).end();
assert(it == disengaged.begin());
assert(disengaged.begin() == it);
assert(it == disengaged.end());
assert(it2 == std::as_const(disengaged).begin());
assert(std::as_const(disengaged).begin() == it2);
assert(it2 == std::as_const(disengaged).end());
}
std::optional<T> engaged{T{}};
{ // end() != begin() if the optional is engaged
auto it = engaged.end();
auto it2 = std::as_const(engaged).end();
assert(it != engaged.begin());
assert(engaged.begin() != it);
assert(it2 != std::as_const(engaged).begin());
assert(std::as_const(engaged).begin() != it2);
}
return true;
}
constexpr bool tests() {
assert(test<int>());
assert(test<char>());
assert(test<const int>());
assert(test<const char>());
return true;
}
int main(int, char**) {
assert(tests());
static_assert(tests());
return 0;
}

View File

@ -0,0 +1,98 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// REQUIRES: std-at-least-c++26
// <optional>
// template <class T> class optional::iterator;
// template <class T> class optional::const_iterator;
#include <cassert>
#include <iterator>
#include <optional>
#include <ranges>
#include <type_traits>
#include <utility>
template <typename T, T __val>
constexpr bool test() {
std::optional<T> opt{__val};
{ // Dereferencing an iterator of an engaged optional will return the same value that the optional holds.
auto it = opt.begin();
auto it2 = std::as_const(opt).begin();
assert(*it == *opt);
assert(*it2 == *std::as_const(opt));
}
{ // optional::iterator and optional::const_iterator satisfy the Cpp17RandomAccessIterator and contiguous iterator.
auto it = opt.begin();
auto it2 = std::as_const(opt).begin();
assert(std::contiguous_iterator<decltype(it)>);
assert(std::contiguous_iterator<decltype(it2)>);
assert(std::random_access_iterator<decltype(it)>);
assert(std::random_access_iterator<decltype(it2)>);
}
{ // const_iterator::value_type == std::remove_cv_t<T>, const_iterator::reference == const T&, iterator::value_type = std::remove_cv_t<T>, iterator::reference == T&
auto it = opt.begin();
auto it2 = std::as_const(opt).begin();
assert((std::is_same_v<typename decltype(it)::value_type, std::remove_cv_t<T>>));
assert((std::is_same_v<typename decltype(it)::reference, T&>));
assert((std::is_same_v<typename decltype(it2)::value_type, std::remove_cv_t<T>>));
assert((std::is_same_v<typename decltype(it2)::reference, const T&>));
}
{ // std::ranges::size for an engaged optional<T> == 1, disengaged optional<T> == 0
const std::optional<T> disengaged{std::nullopt};
std::optional<T> disengaged2{std::nullopt};
assert(std::ranges::size(opt) == 1);
assert(std::ranges::size(std::as_const(opt)) == 1);
assert(std::ranges::size(disengaged) == 0);
assert(std::ranges::size(disengaged2) == 0);
}
{ // std::ranges::enable_view<optional<T>> == true, and std::format_kind<optional<T>> == true
static_assert(std::ranges::enable_view<std::optional<T>> == true);
static_assert(std::format_kind<std::optional<T>> == std::range_format::disabled);
}
// An optional with value that is reset will have a begin() == end(), then when it is reassigned a value,
// begin() != end(), and *begin() will contain the new value.
{
std::optional<T> val{__val};
assert(val.begin() != val.end());
val.reset();
assert(val.begin() == val.end());
val.emplace(__val);
assert(val.begin() != val.end());
assert(*(val.begin()) == __val);
}
return true;
}
constexpr bool tests() {
assert((test<int, 1>()));
assert((test<char, 'a'>()));
assert((test<bool, true>()));
assert((test<const int, 2>()));
assert((test<const char, 'b'>()));
return true;
}
int main(int, char**) {
assert(tests());
static_assert(tests());
return 0;
}

View File

@ -1012,7 +1012,6 @@ feature_test_macros = [
"name": "__cpp_lib_optional_range_support",
"values": {"c++26": 202406}, # P3168R2 Give std::optional Range Support
"headers": ["optional"],
"unimplemented": True,
},
{
"name": "__cpp_lib_out_ptr",