Hello,

On 10/06/2025 16:32, Jonathan Wakely wrote:
Whops, I again addedstd::string to a constexpr test, so this will fail
on the old ABI. Should I change it to e.g.std::vector, or should I keep
it and add a `{ dg-require-effective-target cxx11_abi }` directive?

Would string_view work instead? If not, please change it to vector.

Added both and removed string. New patch attached.

Thank you,

--
Giuseppe D'Angelo
From 767cee328aa59aaae84f72358fa2c5eb0293a5ad Mon Sep 17 00:00:00 2001
From: Giuseppe D'Angelo <giuseppe.dang...@kdab.com>
Date: Mon, 9 Jun 2025 23:13:21 +0200
Subject: [PATCH] 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>; adjust inclusions.
	* 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.

Signed-off-by: Giuseppe D'Angelo <giuseppe.dang...@kdab.com>
---
 libstdc++-v3/include/bits/formatfwd.h         |  64 +++++++
 libstdc++-v3/include/bits/version.def         |   8 +
 libstdc++-v3/include/bits/version.h           |  10 ++
 libstdc++-v3/include/std/format               |  61 -------
 libstdc++-v3/include/std/optional             |  47 +++++
 .../testsuite/20_util/optional/range.cc       | 160 ++++++++++++++++++
 6 files changed, 289 insertions(+), 61 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/20_util/optional/range.cc

diff --git a/libstdc++-v3/include/bits/formatfwd.h b/libstdc++-v3/include/bits/formatfwd.h
index 777e6290f74..f8197cbda72 100644
--- a/libstdc++-v3/include/bits/formatfwd.h
+++ b/libstdc++-v3/include/bits/formatfwd.h
@@ -40,7 +40,10 @@
 #include <concepts>
 #include <type_traits>
 #if __glibcxx_format_ranges // C++ >= 23 && HOSTED
+#  include <tuple>
+#  include <bits/stl_pair.h>     // __is_pair
 #  include <bits/ranges_base.h>  // input_range, range_reference_t
+#  include <bits/utility.h>      // tuple_size_v
 #endif
 
 namespace std _GLIBCXX_VISIBILITY(default)
@@ -126,6 +129,11 @@ namespace __format
       __f.set_debug_format();
     };
 } // namespace __format
+
+  template<typename _Tp, template<typename...> class _Class>
+    constexpr bool __is_specialization_of = false;
+  template<template<typename...> class _Class, typename... _Args>
+    constexpr bool __is_specialization_of<_Class<_Args...>, _Class> = true;
 /// @endcond
 
 #if __glibcxx_format_ranges // C++ >= 23 && HOSTED
@@ -162,6 +170,62 @@ 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>{};
+    }();
+
+  /// @cond undocumented
+  template<typename _Tp>
+    consteval range_format
+    __fmt_kind()
+    {
+      using _Ref = ranges::range_reference_t<_Tp>;
+      if constexpr (is_same_v<remove_cvref_t<_Ref>, _Tp>)
+	return range_format::disabled;
+      else if constexpr (requires { typename _Tp::key_type; })
+	{
+	  if constexpr (requires { typename _Tp::mapped_type; })
+	    {
+	      using _Up = remove_cvref_t<_Ref>;
+	      if constexpr (__is_pair<_Up>)
+		return range_format::map;
+	      else if constexpr (__is_specialization_of<_Up, tuple>)
+		if constexpr (tuple_size_v<_Up> == 2)
+		  return range_format::map;
+	    }
+	  return range_format::set;
+	}
+      else
+	return range_format::sequence;
+    }
+  /// @endcond
+
+  /// A constant determining how a range should be formatted.
+  template<ranges::input_range _Rg> requires same_as<_Rg, remove_cvref_t<_Rg>>
+    constexpr range_format format_kind<_Rg> = __fmt_kind<_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 9ab22cc519f..f864ea11bae 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 371a7ba3b1a..1611132cf21 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 b4929d5ae59..0a7b915f39a 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -412,11 +412,6 @@ namespace __format
     };
 
 /// @cond undocumented
-  template<typename _Tp, template<typename...> class _Class>
-    constexpr bool __is_specialization_of = false;
-  template<template<typename...> class _Class, typename... _Args>
-    constexpr bool __is_specialization_of<_Class<_Args...>, _Class> = true;
-
 namespace __format
 {
   // pre: first != last
@@ -5472,62 +5467,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
-    __fmt_kind()
-    {
-      using _Ref = ranges::range_reference_t<_Tp>;
-      if constexpr (is_same_v<remove_cvref_t<_Ref>, _Tp>)
-	return range_format::disabled;
-      else if constexpr (requires { typename _Tp::key_type; })
-	{
-	  if constexpr (requires { typename _Tp::mapped_type; })
-	    {
-	      using _Up = remove_cvref_t<_Ref>;
-	      if constexpr (__is_pair<_Up>)
-		return range_format::map;
-	      else if constexpr (__is_specialization_of<_Up, tuple>)
-		if constexpr (tuple_size_v<_Up> == 2)
-		  return range_format::map;
-	    }
-	  return range_format::set;
-	}
-      else
-	return range_format::sequence;
-    }
-  /// @endcond
-
-  /// A constant determining how a range should be formatted.
-  template<ranges::input_range _Rg> requires same_as<_Rg, remove_cvref_t<_Rg>>
-    constexpr range_format format_kind<_Rg> = __fmt_kind<_Rg>();
-
 /// @cond undocumented
 namespace __format
 {
diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional
index a616dc07b10..2ae71f11e21 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 00000000000..0d9bed5f167
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/optional/range.cc
@@ -0,0 +1,160 @@
+// { dg-do compile { target c++26 } }
+
+#include <concepts>
+#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::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());
+
-- 
2.43.0

Attachment: smime.p7s
Description: S/MIME Cryptographic Signature

Reply via email to