Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ac4dcbc
Implement P2988R12: Give optional range support
smallp-o-p Jul 15, 2025
3185aa3
Misinputted columns in Cxx2cPapers.csv
smallp-o-p Jul 18, 2025
60f67b2
Address most comments
smallp-o-p Jul 18, 2025
a7740e8
Clang-Format :^)
smallp-o-p Jul 18, 2025
02cdb07
Mostly revert conditional inclusion change (for now) due to a circula…
smallp-o-p Jul 19, 2025
913229a
Remove mentions of implementation in Libc++ 21, should go to Libc++ 2…
smallp-o-p Jul 19, 2025
559825b
Add entry to libc++ 22 release notes
smallp-o-p Jul 19, 2025
3baa163
implicit bool conversions are evil
smallp-o-p Jul 21, 2025
e3398ae
Split test file into three tests
smallp-o-p Jul 21, 2025
3b015e7
Remove usage of remove_cvref_t for pointer and const pointer types si…
smallp-o-p Jul 22, 2025
c8318f1
Fix a comment, forgot a noexcept test
smallp-o-p Jul 22, 2025
ebdc19d
Copy mistake
smallp-o-p Jul 22, 2025
60bf66b
Add hardening with bounded_iter
smallp-o-p Jul 28, 2025
3683469
Fix misplaced parens
smallp-o-p Jul 29, 2025
e9ec733
Remove some transitive includes that were removed with #150583
smallp-o-p Jul 29, 2025
97f3a7b
Remove include guard since it's no longer necessary
smallp-o-p Jul 30, 2025
7cab021
Address comments
smallp-o-p Jul 31, 2025
c0ff08a
Forgot to rename a variable
smallp-o-p Jul 31, 2025
718af43
Use std::as_const pattern for all tests
smallp-o-p Aug 5, 2025
5c60392
Rename pointer type and unguard it
smallp-o-p Aug 5, 2025
d81e017
Disallow T(&)[] and T&(Args...) from having iterator types as pre-cau…
smallp-o-p Aug 5, 2025
10a2271
Forgot an #include
smallp-o-p Aug 6, 2025
65b53af
Retrigger Build
smallp-o-p Aug 6, 2025
1d9ac9a
is_unbounded_array is >= C++20
smallp-o-p Aug 6, 2025
d04201c
Bring in __pointer types too, there is probably a better way to do th…
smallp-o-p Aug 6, 2025
e9d0fa7
Remove unnecessary public:
smallp-o-p Aug 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 "")
2 changes: 1 addition & 1 deletion libcxx/docs/FeatureTestMacroTable.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``
---------------------------------------------------------- -----------------
Expand Down
3 changes: 3 additions & 0 deletions libcxx/docs/ReleaseNotes/22.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ What's New in Libc++ 22.0.0?
==============================

Implemented Papers

- P3168R2: Give ``std::optional`` Range Support (`Github <https://github.com/llvm/llvm-project/issues/105430>`__)

------------------

- P2321R2: ``zip`` (`Github <https://github.com/llvm/llvm-project/issues/105169>`__) (The paper is partially implemented. ``zip_transform_view`` is implemented in this release)
Expand Down
2 changes: 1 addition & 1 deletion libcxx/docs/Status/Cxx2cPapers.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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)","","",""
Expand Down
2 changes: 2 additions & 0 deletions libcxx/include/__iterator/wrap_iter.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ class __wrap_iter {
friend class span;
template <class _Tp, size_t _Size>
friend struct array;
template <class _Tp>
friend class optional;
};

template <class _Iter1>
Expand Down
93 changes: 91 additions & 2 deletions libcxx/include/optional
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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>
Expand All @@ -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>
Expand All @@ -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>
Expand Down Expand Up @@ -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>
Expand All @@ -579,16 +607,49 @@ struct __is_std_optional : false_type {};
template <class _Tp>
struct __is_std_optional<optional<_Tp>> : true_type {};

template <class _Tp, class = void>
struct __optional_iterator_aliases {};

# if _LIBCPP_STD_VER >= 26
// disallow T (&)() and T (&)[]
template <class _Tp>
struct __optional_iterator_aliases<
_Tp,
__enable_if_t<!(is_reference<_Tp>::value && (is_function<__libcpp_remove_reference_t<_Tp>>::value ||
is_unbounded_array<__libcpp_remove_reference_t<_Tp>>::value))> > {
Comment on lines +618 to +619
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it even possible to instantiate optional with an unbounded array or function reference?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not at the moment, since those would fall under optional<T&> which is unimplemented, but since I plan on tackling that later on and the potential issue was mentioned here, I might as well get this out of the way beforehand.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should tackle it then. It's not clear to me that we even want to allow these kinds of instantiations, and I'm very much not a fan of working around problems that don't even exist yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not against it, but I'll ask @ldionne and @frederick-vs-ja what they think about cutting this out for a later patch.

using __pointer _LIBCPP_NODEBUG = std::add_pointer_t<_Tp>;
using __const_pointer _LIBCPP_NODEBUG = std::add_pointer_t<const _Tp>;
# 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

template <class _Tp>
class _LIBCPP_DECLSPEC_EMPTY_BASES optional
: private __optional_move_assign_base<_Tp>,
private __optional_sfinae_ctor_base_t<_Tp>,
private __optional_sfinae_assign_base_t<_Tp> {
using __base _LIBCPP_NODEBUG = __optional_move_assign_base<_Tp>;
private __optional_sfinae_assign_base_t<_Tp>,
public __optional_iterator_aliases<_Tp> {
using __base _LIBCPP_NODEBUG = __optional_move_assign_base<_Tp>;
using __iter_aliases _LIBCPP_NODEBUG = __optional_iterator_aliases<_Tp>;
# if _LIBCPP_STD_VER >= 26
using typename __iter_aliases::__const_pointer;
using typename __iter_aliases::__pointer;
# endif

public:
using value_type = _Tp;

# if _LIBCPP_STD_VER >= 26
using typename __iter_aliases::const_iterator;
using typename __iter_aliases::iterator;
# 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>;
Expand Down Expand Up @@ -792,6 +853,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());
Expand Down
2 changes: 1 addition & 1 deletion libcxx/include/version
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion libcxx/modules/std/optional.inc
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,23 @@
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;

// [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!=;
Expand Down
Original file line number Diff line number Diff line change
@@ -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)>>);
Original file line number Diff line number Diff line change
Expand Up @@ -152,17 +152,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7443,17 +7443,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
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Loading
Loading