On 10/06/25 09:18 +0200, Giuseppe D'Angelo wrote:
Hello,

The attached patch implements C++26's range support for std::optional.

It's also available on Forge here:

https://forge.sourceware.org/gcc/gcc-TEST/pulls/54

It's mostly straightforward, but I had to lift format_kind out of <format> (together with a few other helpers) to avoid <optional> to depend on that. I put it in formatfwd.h, but maybe it ought to go in its own header?

Putting it in formatfwd.h seems fine to me.

OK for trunk with the change to addressof (see comment below).


Thank you,

--
Giuseppe D'Angelo

From 51551a1ee71d254af7aee693921e72976b8337f3 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       | 159 ++++++++++++++++++
6 files changed, 288 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..2a5ab039f67 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

This can use std::addressof. I removed the indirection from addressof
to __addressof so there's no reason to prefer __addressof now, except
in code that needs to compile as C++98.

+       );
+      }
+
+      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();

I know this is precisely how end() is defined in the working draft,
but I'm wondering whether we want to make the implementation of
begin() and end() more similar. So either use has_value() in begin()
instead of this->_M_is_engaged(), or define end() as something like:

    return iterator(this->_M_engaged()
        ? std::addressof(this->_M_get()) + 1, nullptr)

No need to change it in your patch though, I don't have a preference
either way, and it could be changed later if somebody has compelling
rationale why one form is better than another.


+      }
+
+      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..2b004489e10
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/optional/range.cc
@@ -0,0 +1,159 @@
+// { dg-do compile { target c++26 } }
+
+#include <concepts>
+#include <iterator>
+#include <optional>
+#include <ranges>
+#include <string>
+#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("test"));
+  test(std::optional<int>(42));
+  test<const int>(42);
+
+  range_chain_example();
+
+  return true;
+}
+
+static_assert(all_tests());
+
--
2.34.1




Reply via email to