[gcc r16-1487] libstdc++: add range support to std::optional (P3168)
Giuseppe D'Angelo
peppe@gcc.gnu.org
Thu Jun 12 16:30:28 GMT 2025
https://gcc.gnu.org/g:774ae8734f0e199a8c6d29dd8c186b893385470b
commit r16-1487-g774ae8734f0e199a8c6d29dd8c186b893385470b
Author: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
Date: Mon Jun 9 23:13:21 2025 +0200
libstdc++: add range support to std::optional (P3168)
This commit implements P3168 ("Give std::optional Range Support"), added
for C++26. Both begin() and end() are straightforward, implemented using
normal_iterator over a raw pointer.
std::optional is also a view, so specialize enable_view for it.
We also need to disable automatic formatting a std::optional as a range
by specializing format_kind. In order to avoid dragging <format> when
including <optional>, I've isolated format_kind and some supporting code
into <bits/formatfwd.h> so that I can use that (comparatively) lighter
header.
libstdc++-v3/ChangeLog:
* include/bits/formatfwd.h (format_kind): Move the definition
(and some supporting code) from <format>.
* include/std/format (format_kind): Likewise.
* include/bits/version.def (optional_range_support): Add
the feature-testing macro.
* include/bits/version.h: Regenerate.
* include/std/optional (iterator, const_iterator, begin, end):
Add range support.
(enable_view): Specialize for std::optional.
(format_kind): Specialize for std::optional.
* testsuite/20_util/optional/range.cc: New test.
* testsuite/20_util/optional/version.cc: Test the new
feature-testing macro.
Diff:
---
libstdc++-v3/include/bits/formatfwd.h | 26 ++++
libstdc++-v3/include/bits/version.def | 8 +
libstdc++-v3/include/bits/version.h | 10 ++
libstdc++-v3/include/std/format | 26 ----
libstdc++-v3/include/std/optional | 47 ++++++
libstdc++-v3/testsuite/20_util/optional/range.cc | 163 +++++++++++++++++++++
libstdc++-v3/testsuite/20_util/optional/version.cc | 18 +++
7 files changed, 272 insertions(+), 26 deletions(-)
diff --git a/libstdc++-v3/include/bits/formatfwd.h b/libstdc++-v3/include/bits/formatfwd.h
index 777e6290f744..314b55d50bcd 100644
--- a/libstdc++-v3/include/bits/formatfwd.h
+++ b/libstdc++-v3/include/bits/formatfwd.h
@@ -162,6 +162,32 @@ namespace __format
using __maybe_const
= __conditional_t<formattable<const _Tp, _CharT>, const _Tp, _Tp>;
}
+
+ // [format.range], formatting of ranges
+ // [format.range.fmtkind], variable template format_kind
+ enum class range_format {
+ disabled,
+ map,
+ set,
+ sequence,
+ string,
+ debug_string
+ };
+
+ /** @brief A constant determining how a range should be formatted.
+ *
+ * The primary template of `std::format_kind` cannot be instantiated.
+ * There is a partial specialization for input ranges and you can
+ * specialize the variable template for your own cv-unqualified types
+ * that satisfy the `ranges::input_range` concept.
+ *
+ * @since C++23
+ */
+ template<typename _Rg>
+ constexpr auto format_kind = []{
+ static_assert(false, "cannot use primary template of 'std::format_kind'");
+ return type_identity<_Rg>{};
+ }();
#endif // format_ranges
_GLIBCXX_END_NAMESPACE_VERSION
diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index 7cf62e989aa7..880586e91260 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -851,6 +851,14 @@ ftms = {
};
};
+ftms = {
+ name = optional_range_support;
+ values = {
+ v = 202406;
+ cxxmin = 26;
+ };
+};
+
ftms = {
name = destroying_delete;
values = {
diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
index 9f4cf9a3425e..4300adb22762 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -955,6 +955,16 @@
#endif /* !defined(__cpp_lib_optional) && defined(__glibcxx_want_optional) */
#undef __glibcxx_want_optional
+#if !defined(__cpp_lib_optional_range_support)
+# if (__cplusplus > 202302L)
+# define __glibcxx_optional_range_support 202406L
+# if defined(__glibcxx_want_all) || defined(__glibcxx_want_optional_range_support)
+# define __cpp_lib_optional_range_support 202406L
+# endif
+# endif
+#endif /* !defined(__cpp_lib_optional_range_support) && defined(__glibcxx_want_optional_range_support) */
+#undef __glibcxx_want_optional_range_support
+
#if !defined(__cpp_lib_destroying_delete)
# if (__cplusplus >= 202002L) && (__cpp_impl_destroying_delete)
# define __glibcxx_destroying_delete 201806L
diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index 04fb23eb1367..46bd5d5ee6a0 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -5483,32 +5483,6 @@ namespace __format
#endif
#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
- // [format.range], formatting of ranges
- // [format.range.fmtkind], variable template format_kind
- enum class range_format {
- disabled,
- map,
- set,
- sequence,
- string,
- debug_string
- };
-
- /** @brief A constant determining how a range should be formatted.
- *
- * The primary template of `std::format_kind` cannot be instantiated.
- * There is a partial specialization for input ranges and you can
- * specialize the variable template for your own cv-unqualified types
- * that satisfy the `ranges::input_range` concept.
- *
- * @since C++23
- */
- template<typename _Rg>
- constexpr auto format_kind = []{
- static_assert(false, "cannot use primary template of 'std::format_kind'");
- return type_identity<_Rg>{};
- }();
-
/// @cond undocumented
template<typename _Tp>
consteval range_format
diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional
index a616dc07b107..2ae71f11e213 100644
--- a/libstdc++-v3/include/std/optional
+++ b/libstdc++-v3/include/std/optional
@@ -36,6 +36,7 @@
#define __glibcxx_want_freestanding_optional
#define __glibcxx_want_optional
+#define __glibcxx_want_optional_range_support
#define __glibcxx_want_constrained_equality
#include <bits/version.h>
@@ -57,6 +58,11 @@
#if __cplusplus > 202002L
# include <concepts>
#endif
+#ifdef __cpp_lib_optional_range_support // C++ >= 26
+# include <bits/formatfwd.h>
+# include <bits/ranges_base.h>
+# include <bits/stl_iterator.h>
+#endif
namespace std _GLIBCXX_VISIBILITY(default)
{
@@ -858,6 +864,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
public:
using value_type = _Tp;
+#ifdef __cpp_lib_optional_range_support // >= C++26
+ using iterator = __gnu_cxx::__normal_iterator<_Tp*, optional>;
+ using const_iterator = __gnu_cxx::__normal_iterator<const _Tp*, optional>;
+#endif
constexpr optional() noexcept { }
@@ -1158,6 +1168,33 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
}
}
+#ifdef __cpp_lib_optional_range_support // >= C++26
+ // Iterator support.
+ constexpr iterator begin() noexcept
+ {
+ return iterator(
+ this->_M_is_engaged() ? std::addressof(this->_M_get()) : nullptr
+ );
+ }
+
+ constexpr const_iterator begin() const noexcept
+ {
+ return const_iterator(
+ this->_M_is_engaged() ? std::addressof(this->_M_get()) : nullptr
+ );
+ }
+
+ constexpr iterator end() noexcept
+ {
+ return begin() + has_value();
+ }
+
+ constexpr const_iterator end() const noexcept
+ {
+ return begin() + has_value();
+ }
+#endif // __cpp_lib_optional_range_support
+
// Observers.
constexpr const _Tp*
operator->() const noexcept
@@ -1772,6 +1809,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
template <typename _Tp> optional(_Tp) -> optional<_Tp>;
#endif
+#ifdef __cpp_lib_optional_range_support // >= C++26
+ template<typename _Tp>
+ inline constexpr bool
+ ranges::enable_view<optional<_Tp>> = true;
+
+ template<typename _Tp>
+ inline constexpr auto
+ format_kind<optional<_Tp>> = range_format::disabled;
+#endif // __cpp_lib_optional_range_support
+
#undef _GLIBCXX_USE_CONSTRAINTS_FOR_OPTIONAL
_GLIBCXX_END_NAMESPACE_VERSION
diff --git a/libstdc++-v3/testsuite/20_util/optional/range.cc b/libstdc++-v3/testsuite/20_util/optional/range.cc
new file mode 100644
index 000000000000..e77dc21e22b3
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/optional/range.cc
@@ -0,0 +1,163 @@
+// { dg-do compile { target c++26 } }
+
+#include <concepts>
+#include <format>
+#include <iterator>
+#include <optional>
+#include <ranges>
+#include <string_view>
+#include <vector>
+
+#include <testsuite_hooks.h>
+
+template<typename O>
+constexpr
+void
+test_range_concepts()
+{
+ 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);
+}
+
+template<typename O>
+constexpr
+void
+test_iterator_concepts()
+{
+ using T = typename O::value_type;
+ using iterator = typename O::iterator;
+ static_assert(std::contiguous_iterator<iterator>);
+ static_assert(std::is_same_v<typename std::iterator_traits<iterator>::value_type, std::remove_cv_t<T>>);
+ 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&>);
+
+ 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>>);
+ static_assert(std::is_same_v<std::iter_value_t<const_iterator>, std::remove_cv_t<T>>);
+ static_assert(std::is_same_v<typename std::iterator_traits<const_iterator>::reference, const T&>);
+ static_assert(std::is_same_v<std::iter_reference_t<const_iterator>, const T&>);
+}
+
+template<typename O>
+constexpr
+void
+test_empty()
+{
+ O empty;
+ VERIFY(!empty);
+ VERIFY(empty.begin() == empty.end());
+ VERIFY(std::as_const(empty).begin() == std::as_const(empty).end());
+ VERIFY(std::ranges::empty(empty));
+ VERIFY(std::ranges::empty(std::as_const(empty)));
+ VERIFY(std::ranges::empty(empty | std::views::as_const));
+ VERIFY(std::ranges::size(empty) == 0);
+ VERIFY(std::ranges::size(std::as_const(empty)) == 0);
+
+ size_t count = 0;
+ for (const auto& x : empty)
+ ++count;
+ VERIFY(count == 0);
+}
+
+template<typename O, typename T>
+constexpr
+void
+test_non_empty(const T& value)
+{
+ O non_empty = std::make_optional(value);
+ VERIFY(non_empty);
+ 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());
+ VERIFY(std::as_const(non_empty).begin() < std::as_const(non_empty).end());
+ VERIFY(!std::ranges::empty(non_empty));
+ VERIFY(!std::ranges::empty(std::as_const(non_empty)));
+ VERIFY(!std::ranges::empty(non_empty | std::views::as_const));
+ VERIFY(std::ranges::size(non_empty) == 1);
+ VERIFY(std::ranges::size(std::as_const(non_empty)) == 1);
+
+ size_t count = 0;
+ for (const auto& x : non_empty)
+ ++count;
+ VERIFY(count == 1);
+
+ if constexpr (!std::is_const_v<typename O::value_type>) {
+ for (auto& x : non_empty)
+ x = T{};
+ VERIFY(non_empty);
+ VERIFY(*non_empty == T{});
+ }
+}
+
+template<typename T>
+constexpr
+void
+test(const T& value)
+{
+ using O = std::optional<T>;
+ test_range_concepts<O>();
+ test_iterator_concepts<O>();
+ test_empty<O>();
+ test_non_empty<O>(value);
+ static_assert(!std::formattable<O, char>);
+ static_assert(!std::formattable<O, wchar_t>);
+ static_assert(std::format_kind<O> == std::range_format::disabled);
+}
+
+constexpr
+void
+range_chain_example() // from P3168
+{
+ std::vector<int> v{2, 3, 4, 5, 6, 7, 8, 9, 1};
+ auto test = [](int i) -> std::optional<int> {
+ switch(i) {
+ case 1:
+ case 3:
+ case 7:
+ case 9:
+ return i * 2;
+ default:
+ return {};
+ }
+ };
+
+ auto result = v
+ | std::views::transform(test)
+ | std::views::filter([](auto x) { return bool(x); })
+ | std::views::transform([](auto x){ return *x; })
+ | std::ranges::to<std::vector>();
+
+ bool ok = result == std::vector<int>{6, 14, 18, 2};
+ VERIFY(ok);
+}
+
+constexpr
+bool
+all_tests()
+{
+ test(42);
+ int i = 42;
+ test(&i);
+ test(std::string_view("test"));
+ test(std::vector<int>{1, 2, 3, 4});
+ test(std::optional<int>(42));
+ test<const int>(42);
+
+ range_chain_example();
+
+ return true;
+}
+
+static_assert(all_tests());
+
diff --git a/libstdc++-v3/testsuite/20_util/optional/version.cc b/libstdc++-v3/testsuite/20_util/optional/version.cc
index 657a3992422a..ba44aa525356 100644
--- a/libstdc++-v3/testsuite/20_util/optional/version.cc
+++ b/libstdc++-v3/testsuite/20_util/optional/version.cc
@@ -21,8 +21,17 @@
#endif
#endif
+#if __cplusplus > 202302L
+# ifndef __cpp_lib_optional_range_support
+# error "Feature test macro for optional range support is missing in <version>"
+# elif __cpp_lib_optional_range_support != 202406L
+# error "Feature test macro for optional range support has wrong value for C++26 in <version>"
+# endif
+#endif
+
#undef __cpp_lib_optional
#undef __cpp_lib_freestanding_optional
+#undef __cpp_lib_optional_range_support
#include <optional>
#if __cplusplus >= 202302L
@@ -32,3 +41,12 @@
# error "Feature test macro for freestanding std::optional has wrong value in <optional>"
#endif
#endif
+
+#if __cplusplus > 202302L
+# ifndef __cpp_lib_optional_range_support
+# error "Feature test macro for optional range support is missing in <optional>"
+# endif
+# if __cpp_lib_optional_range_support != 202406L
+# error "Feature test macro for optional range support has wrong value for C++26 in <optional>"
+# endif
+#endif
More information about the Gcc-cvs
mailing list