[libcxx-commits] [libcxx] 475cbf0 - [libc++] Implement ranges::iota (#68494)

via libcxx-commits libcxx-commits at lists.llvm.org
Sat Apr 5 04:46:15 PDT 2025


Author: James E T Smith
Date: 2025-04-05T13:46:11+02:00
New Revision: 475cbf0ad6e72f33e5ba5890a1c6e84e39a19e83

URL: https://github.com/llvm/llvm-project/commit/475cbf0ad6e72f33e5ba5890a1c6e84e39a19e83
DIFF: https://github.com/llvm/llvm-project/commit/475cbf0ad6e72f33e5ba5890a1c6e84e39a19e83.diff

LOG: [libc++] Implement ranges::iota (#68494)

# Overview

As a disclaimer, this is my first PR to LLVM and while I've tried to
ensure I've followed the LLVM and libc++ contributing guidelines,
there's probably a good chance I missed something. If I have, just let
me know and I'll try to correct it as soon as I can.

This PR implements `std::ranges::iota` and
`std::ranges::out_value_result` outlined in
[P2440r1](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2440r1.html).

As outlined in the paper above, I've:
- Implemented `out_value_result` and added to `<algorithm>`
- Added `out_value_result`, `iota_result`, and two overloads of `iota`
to `std::ranges` in `<numeric>`
- Updated the version macro `__cpp_lib_ranges_iota` in `<version>`

I've also added tests for `ranges::iota` and `ranges::out_value_result`.
Lastly, I added those structs to the appropriate module files.

Partially implements #105184

EDIT: Forgot to mention in the original post, thanks to @hawkinsw for
taking a look at a preliminary version of this PR!

# TODOs
- [x] Updating the range [status
doc](https://github.com/jamesETsmith/llvm-project/blob/main/libcxx/docs/Status/RangesMajorFeatures.csv)
- [x] Ensure all comments from https://reviews.llvm.org/D121436 are
addressed here
- [X] EDIT (I'll do this in a separate PR). ~~I'm open to implementing
the rest of P2440r1 (`ranges::shift_left` and `ranges::shift_right`) if
that's ok, I just wanted to get feedback on `ranges::iota` first~~
- [x] I've been having trouble building the modules locally and want to
make sure that's working properly

Closes: #134060

Added: 
    libcxx/include/__algorithm/out_value_result.h
    libcxx/include/__numeric/ranges_iota.h
    libcxx/test/std/algorithms/algorithms.results/out_value_result.pass.cpp
    libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp

Modified: 
    libcxx/docs/FeatureTestMacroTable.rst
    libcxx/docs/Status/Cxx23.rst
    libcxx/docs/Status/Cxx23Papers.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/algorithm
    libcxx/include/module.modulemap
    libcxx/include/numeric
    libcxx/include/version
    libcxx/modules/std/algorithm.inc
    libcxx/modules/std/numeric.inc
    libcxx/test/std/algorithms/algorithms.results/no_unique_address.compile.pass.cpp
    libcxx/test/std/algorithms/ranges_result_alias_declarations.compile.pass.cpp
    libcxx/test/std/algorithms/ranges_robust_against_dangling.pass.cpp
    libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp
    libcxx/test/std/language.support/support.limits/support.limits.general/numeric.version.compile.pass.cpp
    libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
    libcxx/test/support/test_iterators.h
    libcxx/utils/generate_feature_test_macro_components.py

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index bbcc76b52f0a9..9015ccb18dddf 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -374,7 +374,7 @@ Status
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_ranges_find_last``                             ``202207L``
     ---------------------------------------------------------- -----------------
-    ``__cpp_lib_ranges_iota``                                  *unimplemented*
+    ``__cpp_lib_ranges_iota``                                  ``202202L``
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_ranges_join_with``                             *unimplemented*
     ---------------------------------------------------------- -----------------

diff  --git a/libcxx/docs/Status/Cxx23.rst b/libcxx/docs/Status/Cxx23.rst
index 3a922adcb0235..0f9d4bf0d1af8 100644
--- a/libcxx/docs/Status/Cxx23.rst
+++ b/libcxx/docs/Status/Cxx23.rst
@@ -40,4 +40,4 @@ Library Working Group Issues Status
 .. csv-table::
    :file: Cxx23Issues.csv
    :header-rows: 1
-   :widths: auto
+   :widths: auto
\ No newline at end of file

diff  --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv
index 4e45debd419ef..923d8bf9341d0 100644
--- a/libcxx/docs/Status/Cxx23Papers.csv
+++ b/libcxx/docs/Status/Cxx23Papers.csv
@@ -46,7 +46,7 @@
 "`P2255R2 <https://wg21.link/P2255R2>`__","A type trait to detect reference binding to temporary","2022-02 (Virtual)","|Partial|","","Implemented the type traits only."
 "`P2273R3 <https://wg21.link/P2273R3>`__","Making ``std::unique_ptr`` constexpr","2022-02 (Virtual)","|Complete|","16",""
 "`P2387R3 <https://wg21.link/P2387R3>`__","Pipe support for user-defined range adaptors","2022-02 (Virtual)","|Complete|","19",""
-"`P2440R1 <https://wg21.link/P2440R1>`__","``ranges::iota``, ``ranges::shift_left`` and ``ranges::shift_right``","2022-02 (Virtual)","","",""
+"`P2440R1 <https://wg21.link/P2440R1>`__","``ranges::iota``, ``ranges::shift_left`` and ``ranges::shift_right``","2022-02 (Virtual)","|Partial|","","Only ``ranges::iota`` is implemented."
 "`P2441R2 <https://wg21.link/P2441R2>`__","``views::join_with``","2022-02 (Virtual)","|In Progress|","",""
 "`P2442R1 <https://wg21.link/P2442R1>`__","Windowing range adaptors: ``views::chunk`` and ``views::slide``","2022-02 (Virtual)","","",""
 "`P2443R1 <https://wg21.link/P2443R1>`__","``views::chunk_by``","2022-02 (Virtual)","|Complete|","18",""

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index a021b9bb44d67..7b09beb74b173 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -64,6 +64,7 @@ set(files
   __algorithm/next_permutation.h
   __algorithm/none_of.h
   __algorithm/nth_element.h
+  __algorithm/out_value_result.h
   __algorithm/partial_sort.h
   __algorithm/partial_sort_copy.h
   __algorithm/partition.h
@@ -615,6 +616,7 @@ set(files
   __numeric/midpoint.h
   __numeric/partial_sum.h
   __numeric/pstl.h
+  __numeric/ranges_iota.h
   __numeric/reduce.h
   __numeric/saturation_arithmetic.h
   __numeric/transform_exclusive_scan.h

diff  --git a/libcxx/include/__algorithm/out_value_result.h b/libcxx/include/__algorithm/out_value_result.h
new file mode 100644
index 0000000000000..9e1e0e07286d4
--- /dev/null
+++ b/libcxx/include/__algorithm/out_value_result.h
@@ -0,0 +1,56 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___ALGORITHM_OUT_VALUE_RESULT_H
+#define _LIBCPP___ALGORITHM_OUT_VALUE_RESULT_H
+
+#include <__concepts/convertible_to.h>
+#include <__config>
+#include <__utility/move.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 23
+
+namespace ranges {
+
+template <class _OutIter1, class _ValType1>
+struct out_value_result {
+  _LIBCPP_NO_UNIQUE_ADDRESS _OutIter1 out;
+  _LIBCPP_NO_UNIQUE_ADDRESS _ValType1 value;
+
+  template <class _OutIter2, class _ValType2>
+    requires convertible_to<const _OutIter1&, _OutIter2> && convertible_to<const _ValType1&, _ValType2>
+  _LIBCPP_HIDE_FROM_ABI constexpr operator out_value_result<_OutIter2, _ValType2>() const& {
+    return {out, value};
+  }
+
+  template <class _OutIter2, class _ValType2>
+    requires convertible_to<_OutIter1, _OutIter2> && convertible_to<_ValType1, _ValType2>
+  _LIBCPP_HIDE_FROM_ABI constexpr operator out_value_result<_OutIter2, _ValType2>() && {
+    return {std::move(out), std::move(value)};
+  }
+};
+
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER >= 23
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___ALGORITHM_OUT_VALUE_RESULT_H

diff  --git a/libcxx/include/__numeric/ranges_iota.h b/libcxx/include/__numeric/ranges_iota.h
new file mode 100644
index 0000000000000..bf53dd85fecef
--- /dev/null
+++ b/libcxx/include/__numeric/ranges_iota.h
@@ -0,0 +1,65 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___NUMERIC_RANGES_IOTA_H
+#define _LIBCPP___NUMERIC_RANGES_IOTA_H
+
+#include <__algorithm/out_value_result.h>
+#include <__config>
+#include <__iterator/concepts.h>
+#include <__ranges/access.h>
+#include <__ranges/concepts.h>
+#include <__ranges/dangling.h>
+#include <__utility/as_const.h>
+#include <__utility/move.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 23
+namespace ranges {
+template <typename _Out, typename _Tp>
+using iota_result = ranges::out_value_result<_Out, _Tp>;
+
+struct __iota_fn {
+public:
+  template <input_or_output_iterator _Out, sentinel_for<_Out> _Sent, weakly_incrementable _Tp>
+    requires indirectly_writable<_Out, const _Tp&>
+  _LIBCPP_HIDE_FROM_ABI static constexpr iota_result<_Out, _Tp> operator()(_Out __first, _Sent __last, _Tp __value) {
+    while (__first != __last) {
+      *__first = std::as_const(__value);
+      ++__first;
+      ++__value;
+    }
+    return {std::move(__first), std::move(__value)};
+  }
+
+  template <weakly_incrementable _Tp, ranges::output_range<const _Tp&> _Range>
+  _LIBCPP_HIDE_FROM_ABI static constexpr iota_result<ranges::borrowed_iterator_t<_Range>, _Tp>
+  operator()(_Range&& __r, _Tp __value) {
+    return operator()(ranges::begin(__r), ranges::end(__r), std::move(__value));
+  }
+};
+
+inline constexpr auto iota = __iota_fn{};
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER >= 23
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___NUMERIC_RANGES_IOTA_H

diff  --git a/libcxx/include/algorithm b/libcxx/include/algorithm
index 6ba903ad3ce1e..bf67d3363a595 100644
--- a/libcxx/include/algorithm
+++ b/libcxx/include/algorithm
@@ -45,6 +45,9 @@ namespace ranges {
   template <class I, class T>
     struct in_value_result;              // since C++23
 
+  template <class O, class T>
+    struct out_value_result;             // since C++23
+
   template<forward_iterator I, sentinel_for<I> S, class Proj = identity,
     indirect_strict_weak_order<projected<I, Proj>> Comp = ranges::less>                                   // since C++20
   constexpr I min_element(I first, S last, Comp comp = {}, Proj proj = {});
@@ -1936,6 +1939,7 @@ template <class BidirectionalIterator, class Compare>
 #    include <__algorithm/in_out_result.h>
 #    include <__algorithm/lexicographical_compare_three_way.h>
 #    include <__algorithm/min_max_result.h>
+#    include <__algorithm/out_value_result.h>
 #    include <__algorithm/ranges_adjacent_find.h>
 #    include <__algorithm/ranges_all_of.h>
 #    include <__algorithm/ranges_any_of.h>

diff  --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index 0ce42fc4d3633..324931b1bb078 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -490,6 +490,7 @@ module std [system] {
     module next_permutation                       { header "__algorithm/next_permutation.h" }
     module none_of                                { header "__algorithm/none_of.h" }
     module nth_element                            { header "__algorithm/nth_element.h" }
+    module out_value_result                       { header "__algorithm/out_value_result.h" }
     module partial_sort_copy                      { header "__algorithm/partial_sort_copy.h" }
     module partial_sort                           { header "__algorithm/partial_sort.h" }
     module partition_copy                         { header "__algorithm/partition_copy.h" }
@@ -1727,6 +1728,7 @@ module std [system] {
     module partial_sum                { header "__numeric/partial_sum.h" }
     module pstl                       { header "__numeric/pstl.h" }
     module reduce                     { header "__numeric/reduce.h" }
+    module ranges_iota                { header "__numeric/ranges_iota.h" }
     module saturation_arithmetic      { header "__numeric/saturation_arithmetic.h" }
     module transform_exclusive_scan   { header "__numeric/transform_exclusive_scan.h" }
     module transform_inclusive_scan   { header "__numeric/transform_inclusive_scan.h" }

diff  --git a/libcxx/include/numeric b/libcxx/include/numeric
index 2f2b86136fb98..48c330fcb009c 100644
--- a/libcxx/include/numeric
+++ b/libcxx/include/numeric
@@ -172,6 +172,7 @@ constexpr T saturate_cast(U x) noexcept;                    // freestanding, Sin
 #    include <__numeric/gcd_lcm.h>
 #    include <__numeric/inclusive_scan.h>
 #    include <__numeric/pstl.h>
+#    include <__numeric/ranges_iota.h>
 #    include <__numeric/reduce.h>
 #    include <__numeric/transform_exclusive_scan.h>
 #    include <__numeric/transform_inclusive_scan.h>

diff  --git a/libcxx/include/version b/libcxx/include/version
index 83ae11dabd2bc..49102716c3605 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -513,7 +513,7 @@ __cpp_lib_void_t                                        201411L <type_traits>
 # define __cpp_lib_ranges_chunk_by                      202202L
 # define __cpp_lib_ranges_contains                      202207L
 # define __cpp_lib_ranges_find_last                     202207L
-// # define __cpp_lib_ranges_iota                          202202L
+# define __cpp_lib_ranges_iota                          202202L
 // # define __cpp_lib_ranges_join_with                     202202L
 # define __cpp_lib_ranges_repeat                        202207L
 // # define __cpp_lib_ranges_slide                         202202L

diff  --git a/libcxx/modules/std/algorithm.inc b/libcxx/modules/std/algorithm.inc
index 3c2139cd64ee4..95c05f01e5562 100644
--- a/libcxx/modules/std/algorithm.inc
+++ b/libcxx/modules/std/algorithm.inc
@@ -20,7 +20,9 @@ export namespace std {
     using std::ranges::in_value_result;
 #endif
     using std::ranges::min_max_result;
-    // using std::ranges::out_value_result;
+#if _LIBCPP_STD_VER >= 23
+    using std::ranges::out_value_result;
+#endif
   } // namespace ranges
 
   // [alg.nonmodifying], non-modifying sequence operations

diff  --git a/libcxx/modules/std/numeric.inc b/libcxx/modules/std/numeric.inc
index 3bc7b23168158..5a549552081d2 100644
--- a/libcxx/modules/std/numeric.inc
+++ b/libcxx/modules/std/numeric.inc
@@ -42,8 +42,12 @@ export namespace std {
   using std::iota;
 
   namespace ranges {
-    // using std::ranges::iota_result;
-    // using std::ranges::iota;
+
+#if _LIBCPP_STD_VER >= 23
+    using std::ranges::iota;
+    using std::ranges::iota_result;
+#endif // _LIBCPP_STD_VER >= 23
+
   } // namespace ranges
 
   // [numeric.ops.gcd], greatest common divisor

diff  --git a/libcxx/test/std/algorithms/algorithms.results/no_unique_address.compile.pass.cpp b/libcxx/test/std/algorithms/algorithms.results/no_unique_address.compile.pass.cpp
index 34dbd64a49ae8..70da332c56e2f 100644
--- a/libcxx/test/std/algorithms/algorithms.results/no_unique_address.compile.pass.cpp
+++ b/libcxx/test/std/algorithms/algorithms.results/no_unique_address.compile.pass.cpp
@@ -53,7 +53,10 @@ static_assert(sizeof(std::ranges::in_out_out_result<Empty, Empty, char>) == 2);
 static_assert(sizeof(std::ranges::in_out_out_result<int, Empty, Empty2>) == sizeof(int));
 static_assert(sizeof(std::ranges::in_out_out_result<Empty, Empty, Empty>) == 3);
 
-#if TEST_STD_VER >= 23
+#if _LIBCPP_STD_VER >= 23
+static_assert(sizeof(std::ranges::out_value_result<Empty, int>) == sizeof(int));
+static_assert(sizeof(std::ranges::out_value_result<int, Empty>) == sizeof(int));
+static_assert(sizeof(std::ranges::out_value_result<Empty, Empty>) == 2);
 
 static_assert(sizeof(std::ranges::in_value_result<Empty, int>) == sizeof(int));
 static_assert(sizeof(std::ranges::in_value_result<int, Empty>) == sizeof(int));

diff  --git a/libcxx/test/std/algorithms/algorithms.results/out_value_result.pass.cpp b/libcxx/test/std/algorithms/algorithms.results/out_value_result.pass.cpp
new file mode 100644
index 0000000000000..7bdbb7f60e9a2
--- /dev/null
+++ b/libcxx/test/std/algorithms/algorithms.results/out_value_result.pass.cpp
@@ -0,0 +1,141 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+//  template <class O, class T>
+//    struct out_value_result;
+
+#include <algorithm>
+#include <cassert>
+#include <type_traits>
+
+#include "MoveOnly.h"
+
+using std::ranges::out_value_result;
+
+//
+// Helper structs
+//
+
+// only explicit construction
+struct IterTypeExplicit {
+  explicit IterTypeExplicit(int*);
+};
+
+// implicit construction
+struct IterTypeImplicit {
+  IterTypeImplicit(int*);
+};
+
+struct IterTypeImplicitRef {
+  IterTypeImplicitRef(int&);
+};
+
+struct NotConvertible {};
+
+template <class T>
+struct ConvertibleFrom {
+  constexpr ConvertibleFrom(T c) : content{c} {}
+  T content;
+};
+
+// Standard layout classes can't have virtual functions
+struct NonStandardLayoutTypeBase {
+  virtual ~NonStandardLayoutTypeBase();
+};
+struct NonStandardLayoutType : public NonStandardLayoutTypeBase {};
+
+//
+constexpr bool test_constraints() {
+  // requires convertible_to<const _OutIter1&, _OutIter2> && convertible_to<const _ValType1&, _ValType2>
+  static_assert(std::is_constructible_v<out_value_result<int*, int>, out_value_result<int*, int>>);
+
+  // test failure when implicit conversion isn't allowed
+  static_assert(!std::is_constructible_v<out_value_result<IterTypeExplicit, int>, out_value_result<int*, int>>);
+
+  // test success when implicit conversion is allowed, checking combinations of value, reference, and const
+  static_assert(std::is_constructible_v<out_value_result<IterTypeImplicit, int>, out_value_result<int*, int>>);
+  static_assert(std::is_constructible_v<out_value_result<IterTypeImplicit, int>, out_value_result<int*, int> const>);
+  static_assert(std::is_constructible_v<out_value_result<IterTypeImplicit, int>, out_value_result<int*, int>&>);
+  static_assert(std::is_constructible_v<out_value_result<IterTypeImplicit, int>, out_value_result<int*, int> const&>);
+
+  static_assert(!std::is_constructible_v<out_value_result<IterTypeImplicitRef, int>, out_value_result<int, int>&>);
+
+  // has to be convertible via const&
+  static_assert(std::is_convertible_v<out_value_result<int, int>&, out_value_result<long, long>>);
+  static_assert(std::is_convertible_v<const out_value_result<int, int>&, out_value_result<long, long>>);
+  static_assert(std::is_convertible_v<out_value_result<int, int>&&, out_value_result<long, long>>);
+  static_assert(std::is_convertible_v<const out_value_result<int, int>&&, out_value_result<long, long>>);
+
+  // should be move constructible
+  static_assert(std::is_move_constructible_v<out_value_result<MoveOnly, int>>);
+  static_assert(std::is_move_constructible_v<out_value_result<int, MoveOnly>>);
+
+  // conversions should not work if there is no conversion
+  static_assert(!std::is_convertible_v<out_value_result<NotConvertible, int>, out_value_result<int, NotConvertible>>);
+  static_assert(!std::is_convertible_v<out_value_result<int, NotConvertible>, out_value_result<NotConvertible, int>>);
+
+  // check standard layout
+  static_assert(std::is_standard_layout_v<out_value_result<int, int>>);
+  static_assert(!std::is_standard_layout_v<out_value_result<NonStandardLayoutType, int>>);
+
+  return true;
+}
+
+// Test results
+constexpr bool test() {
+  {
+    // Check that conversion operator works
+    out_value_result<double, int> res{10, 1};
+    assert(res.out == 10);
+    assert(res.value == 1);
+    out_value_result<ConvertibleFrom<double>, ConvertibleFrom<int>> res2 = res;
+    assert(res2.out.content == 10);
+    assert(res2.value.content == 1);
+  }
+  {
+    // Check that out_value_result isn't overconstrained w.r.t. move/copy constructors
+    out_value_result<MoveOnly, int> res{MoveOnly{}, 10};
+    assert(res.out.get() == 1);
+    assert(res.value == 10);
+    auto res2 = std::move(res);
+    assert(res.out.get() == 0);
+    assert(res.value == 10);
+    assert(res2.out.get() == 1);
+    assert(res2.value == 10);
+  }
+  {
+    // Check structured binding
+    auto [out, val] = out_value_result<int, int>{1, 2};
+    assert(out == 1);
+    assert(val == 2);
+  }
+  {
+    // Check default construction
+    out_value_result<int, double> res;
+    static_assert(std::is_same_v<int, decltype(res.out)>);
+    static_assert(std::is_same_v<double, decltype(res.value)>);
+  }
+  {
+    // Check aggregate initiazliation
+    out_value_result<int, int> res = {1, 2};
+    assert(res.out == 1);
+    assert(res.value == 2);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test_constraints();
+  static_assert(test_constraints());
+  test();
+  static_assert(test());
+  return 0;
+}

diff  --git a/libcxx/test/std/algorithms/ranges_result_alias_declarations.compile.pass.cpp b/libcxx/test/std/algorithms/ranges_result_alias_declarations.compile.pass.cpp
index 6940b23cfca1e..dcf25099dbc47 100644
--- a/libcxx/test/std/algorithms/ranges_result_alias_declarations.compile.pass.cpp
+++ b/libcxx/test/std/algorithms/ranges_result_alias_declarations.compile.pass.cpp
@@ -13,6 +13,7 @@
 // ensure that all result alias declarations are defined
 
 #include <algorithm>
+#include <numeric>
 #include <memory>
 #include <type_traits>
 
@@ -62,9 +63,6 @@ static_assert(std::is_same_v<in_found_result<int>, next_permutation_result<int>>
 static_assert(std::is_same_v<in_found_result<int>, prev_permutation_result<int>>);
 
 #if TEST_STD_VER >= 23
-
 static_assert(std::is_same_v<in_value_result<int, long>, fold_left_with_iter_result<int, long>>);
-
-// static_assert(std::is_same_v<out_value_result<int>, iota_result<int>>);
-
+static_assert(std::is_same_v<out_value_result<int, int>, iota_result<int, int>>);
 #endif // TEST_STD_VER

diff  --git a/libcxx/test/std/algorithms/ranges_robust_against_dangling.pass.cpp b/libcxx/test/std/algorithms/ranges_robust_against_dangling.pass.cpp
index 2691269ef1b28..e0a6aaaf03aa0 100644
--- a/libcxx/test/std/algorithms/ranges_robust_against_dangling.pass.cpp
+++ b/libcxx/test/std/algorithms/ranges_robust_against_dangling.pass.cpp
@@ -18,11 +18,13 @@
 #include <concepts>
 #include <functional>
 #include <iterator>
+#include <numeric>
 #include <ranges>
 #include <random>
 #include <utility>
 
 #include "test_iterators.h"
+#include "test_macros.h"
 
 struct NonBorrowedRange {
   using Iter = int*;
@@ -78,6 +80,9 @@ constexpr bool test_all() {
   using std::ranges::move_result;
   using std::ranges::move_backward_result;
   using std::ranges::next_permutation_result;
+#if TEST_STD_VER >= 23
+  using std::ranges::out_value_result;
+#endif
   using std::ranges::partial_sort_copy_result;
   using std::ranges::partition_copy_result;
   using std::ranges::prev_permutation_result;
@@ -217,6 +222,10 @@ constexpr bool test_all() {
   dangling_1st<prev_permutation_result<dangling>>(std::ranges::prev_permutation, in);
   dangling_1st<next_permutation_result<dangling>>(std::ranges::next_permutation, in);
 
+#if TEST_STD_VER >= 23
+  dangling_1st<out_value_result<dangling, decltype(x)>>(std::ranges::iota, in, x);
+#endif
+
   return true;
 }
 

diff  --git a/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp b/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp
index bebaeb01f22e9..9d4b0d608518a 100644
--- a/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp
+++ b/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp
@@ -14,6 +14,7 @@
 // a customization point) rather than plain `swap` (which might not work with certain valid iterators).
 
 #include <algorithm>
+#include <numeric>
 
 #include <array>
 #include <concepts>
@@ -28,22 +29,22 @@
 #include "test_macros.h"
 
 // (in, ...)
-template <class Func, std::ranges::range Input, class ...Args>
-constexpr void test(Func&& func, Input& in, Args&& ...args) {
+template <class Func, std::ranges::range Input, class... Args>
+constexpr void test(Func&& func, Input& in, Args&&... args) {
   (void)func(in.begin(), in.end(), std::forward<Args>(args)...);
   (void)func(in, std::forward<Args>(args)...);
 }
 
 // (in1, in2, ...)
-template <class Func, std::ranges::range Range1, std::ranges::range Range2, class ...Args>
-constexpr void test(Func&& func, Range1& r1, Range2& r2, Args&& ...args) {
+template <class Func, std::ranges::range Range1, std::ranges::range Range2, class... Args>
+constexpr void test(Func&& func, Range1& r1, Range2& r2, Args&&... args) {
   (void)func(r1.begin(), r1.end(), r2.begin(), r2.end(), std::forward<Args>(args)...);
   (void)func(r1, r2, std::forward<Args>(args)...);
 }
 
 // (in, mid, ...)
-template <class Func, std::ranges::range Input, class ...Args>
-constexpr void test_mid(Func&& func, Input& in, std::ranges::iterator_t<Input> mid, Args&& ...args) {
+template <class Func, std::ranges::range Input, class... Args>
+constexpr void test_mid(Func&& func, Input& in, std::ranges::iterator_t<Input> mid, Args&&... args) {
   (void)func(in.begin(), mid, in.end(), std::forward<Args>(args)...);
   (void)func(in, mid, std::forward<Args>(args)...);
 }
@@ -68,9 +69,9 @@ constexpr void run_tests() {
   Proxy<T&> x{num};
   int count = 1;
 
-  auto unary_pred = [](const Proxy<T&>&) { return true; };
+  auto unary_pred  = [](const Proxy<T&>&) { return true; };
   auto binary_func = [](const Proxy<T>&, const Proxy<T>&) -> Proxy<T> { return Proxy<T>(T()); };
-  auto gen = [] { return Proxy<T>(T{42}); };
+  auto gen         = [] { return Proxy<T>(T{42}); };
 
   test(std::ranges::any_of, in, unary_pred);
   test(std::ranges::all_of, in, unary_pred);
@@ -106,6 +107,11 @@ constexpr void run_tests() {
   test(std::ranges::search, in, in2);
   test(std::ranges::search_n, in, count, x);
   test(std::ranges::find_end, in, in2);
+#if TEST_STD_VER >= 23
+  if constexpr (std::weakly_incrementable<T>) {
+    test(std::ranges::iota, in, x);
+  }
+#endif
   test(std::ranges::is_partitioned, in, unary_pred);
   test(std::ranges::is_sorted, in);
   test(std::ranges::is_sorted_until, in);

diff  --git a/libcxx/test/std/language.support/support.limits/support.limits.general/numeric.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/numeric.version.compile.pass.cpp
index d132b7c7b9c4f..0743c6c71840c 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/numeric.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/numeric.version.compile.pass.cpp
@@ -197,17 +197,11 @@
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_iota
-#     error "__cpp_lib_ranges_iota should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_iota != 202202L
-#     error "__cpp_lib_ranges_iota should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_iota
-#     error "__cpp_lib_ranges_iota should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_iota
+#   error "__cpp_lib_ranges_iota should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_iota != 202202L
+#   error "__cpp_lib_ranges_iota should have the value 202202L in c++23"
 # endif
 
 # ifdef __cpp_lib_saturation_arithmetic
@@ -250,17 +244,11 @@
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_iota
-#     error "__cpp_lib_ranges_iota should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_iota != 202202L
-#     error "__cpp_lib_ranges_iota should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_iota
-#     error "__cpp_lib_ranges_iota should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_iota
+#   error "__cpp_lib_ranges_iota should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_iota != 202202L
+#   error "__cpp_lib_ranges_iota should have the value 202202L in c++26"
 # endif
 
 # ifndef __cpp_lib_saturation_arithmetic

diff  --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index e5a657207923b..07e96e53f2e93 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -5771,17 +5771,11 @@
 #   error "__cpp_lib_ranges_find_last should have the value 202207L in c++23"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_iota
-#     error "__cpp_lib_ranges_iota should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_iota != 202202L
-#     error "__cpp_lib_ranges_iota should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_iota
-#     error "__cpp_lib_ranges_iota should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_iota
+#   error "__cpp_lib_ranges_iota should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_iota != 202202L
+#   error "__cpp_lib_ranges_iota should have the value 202202L in c++23"
 # endif
 
 # if !defined(_LIBCPP_VERSION)
@@ -7664,17 +7658,11 @@
 #   error "__cpp_lib_ranges_find_last should have the value 202207L in c++26"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_iota
-#     error "__cpp_lib_ranges_iota should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_iota != 202202L
-#     error "__cpp_lib_ranges_iota should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_iota
-#     error "__cpp_lib_ranges_iota should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_iota
+#   error "__cpp_lib_ranges_iota should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_iota != 202202L
+#   error "__cpp_lib_ranges_iota should have the value 202202L in c++26"
 # endif
 
 # if !defined(_LIBCPP_VERSION)

diff  --git a/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp
new file mode 100644
index 0000000000000..9fa50f1326f1d
--- /dev/null
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp
@@ -0,0 +1,215 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// Testing std::ranges::iota
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <numeric>
+#include <utility>
+
+#include "almost_satisfies_types.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+//
+// Testing constraints
+//
+
+// Concepts to check 
diff erent overloads of std::ranges::iota
+template <class Iter = int*, class Sent = int*, class Value = int>
+concept HasIotaIter = requires(Iter&& iter, Sent&& sent, Value&& val) {
+  std::ranges::iota(std::forward<Iter>(iter), std::forward<Sent>(sent), std::forward<Value>(val));
+};
+
+template <class Range, class Value = int>
+concept HasIotaRange =
+    requires(Range&& range, Value&& val) { std::ranges::iota(std::forward<Range>(range), std::forward<Value>(val)); };
+
+// Test constraints of the iterator/sentinel overload
+// ==================================================
+static_assert(HasIotaIter<int*, int*, int>);
+
+// !input_or_output_iterator<O>
+static_assert(!HasIotaIter<InputIteratorNotInputOrOutputIterator>);
+
+// !sentinel_for<S, O>
+static_assert(!HasIotaIter<int*, SentinelForNotSemiregular>);
+static_assert(!HasIotaIter<int*, SentinelForNotWeaklyEqualityComparableWith>);
+
+// !weakly_incrementable<T>
+static_assert(!HasIotaIter<int*, int*, WeaklyIncrementableNotMovable>);
+
+// !indirectly writable <O, T>
+static_assert(!HasIotaIter<OutputIteratorNotIndirectlyWritable, int*, int>);
+
+// Test constraints for the range overload
+// =======================================
+static_assert(HasIotaRange<UncheckedRange<int*>, int>);
+
+// !weakly_incrementable<T>
+static_assert(!HasIotaRange<UncheckedRange<int*>, WeaklyIncrementableNotMovable>);
+
+// !ranges::output_range<const _Tp&>
+static_assert(!HasIotaRange<UncheckedRange<int*>, OutputIteratorNotIndirectlyWritable>);
+
+//
+// Testing results
+//
+
+struct DangerousCopyAssign {
+  int val;
+  using 
diff erence_type = int;
+
+  constexpr explicit DangerousCopyAssign(int v) : val(v) {}
+
+  // Needed in postfix
+  constexpr DangerousCopyAssign(DangerousCopyAssign const& other) { this->val = other.val; }
+
+  /*
+  This class has a "mischievous" non-const overload of copy-assignment
+  operator that modifies the object being assigned from. `ranges::iota`
+  should not be invoking this overload thanks to the `std::as_const` in its
+  implementation. If for some reason it does invoke it, there will be a compiler
+  error.
+  */
+  constexpr DangerousCopyAssign& operator=(DangerousCopyAssign& a) = delete;
+
+  // safe copy assignment std::as_const inside ranges::iota should ensure this
+  // overload gets called
+  constexpr DangerousCopyAssign& operator=(DangerousCopyAssign const& a) {
+    this->val = a.val;
+    return *this;
+  }
+
+  constexpr bool operator==(DangerousCopyAssign const& rhs) { return this->val == rhs.val; }
+
+  // prefix
+  constexpr DangerousCopyAssign& operator++() {
+    ++(this->val);
+    return *this;
+  }
+
+  // postfix
+  constexpr DangerousCopyAssign operator++(int) {
+    auto tmp = *this;
+    ++this->val;
+    return tmp;
+  }
+};
+
+template <class Iter, class Sent, std::size_t N>
+constexpr void test_result(std::array<int, N> input, int starting_value, std::array<int, N> const expected) {
+  { // (iterator, sentinel) overload
+    auto in_begin = Iter(input.data());
+    auto in_end   = Sent(Iter(input.data() + input.size()));
+    std::same_as<std::ranges::out_value_result<Iter, int>> decltype(auto) result =
+        std::ranges::iota(std::move(in_begin), std::move(in_end), starting_value);
+    assert(result.out == in_end);
+    assert(result.value == starting_value + static_cast<int>(N));
+    assert(std::ranges::equal(input, expected));
+  }
+
+  { // (range) overload
+    // in the range overload adds the additional constraint that it must be an output range
+    // so skip this for the input iterators we test
+    auto in_begin = Iter(input.data());
+    auto in_end   = Sent(Iter(input.data() + input.size()));
+    auto range    = std::ranges::subrange(std::move(in_begin), std::move(in_end));
+
+    std::same_as<std::ranges::out_value_result<Iter, int>> decltype(auto) result =
+        std::ranges::iota(range, starting_value);
+    assert(result.out == in_end);
+    assert(result.value == starting_value + static_cast<int>(N));
+    assert(std::ranges::equal(input, expected));
+  }
+}
+
+template <class Iter, class Sent = sentinel_wrapper<Iter>>
+constexpr void test_results() {
+  // Empty
+  test_result<Iter, Sent, 0>({}, 0, {});
+  // 1-element sequence
+  test_result<Iter, Sent, 1>({1}, 0, {0});
+  // Longer sequence
+  test_result<Iter, Sent, 5>({1, 2, 3, 4, 5}, 0, {0, 1, 2, 3, 4});
+}
+
+constexpr void test_user_defined_type() {
+  // Simple non-fundamental type
+  struct UserDefinedType {
+    int val;
+    using 
diff erence_type = int;
+
+    constexpr explicit UserDefinedType(int v) : val(v) {}
+    constexpr UserDefinedType(UserDefinedType const& other) { this->val = other.val; }
+    constexpr UserDefinedType& operator=(UserDefinedType const& a) {
+      this->val = a.val;
+      return *this;
+    }
+
+    // prefix
+    constexpr UserDefinedType& operator++() {
+      ++(this->val);
+      return *this;
+    }
+
+    // postfix
+    constexpr UserDefinedType operator++(int) {
+      auto tmp = *this;
+      ++this->val;
+      return tmp;
+    }
+  };
+
+  // Setup
+  using A                                 = UserDefinedType;
+  std::array<UserDefinedType, 5> a        = {A{0}, A{0}, A{0}, A{0}, A{0}};
+  std::array<UserDefinedType, 5> expected = {A{0}, A{1}, A{2}, A{3}, A{4}};
+
+  // Fill with values
+  std::ranges::iota(a, A{0});
+  auto proj_val = [](UserDefinedType const& el) { return el.val; };
+
+  // Check
+  assert(std::ranges::equal(a, expected, std::ranges::equal_to{}, proj_val, proj_val));
+}
+
+constexpr void test_dangerous_copy_assign() {
+  using A = DangerousCopyAssign;
+
+  // If the dangerous non-const copy assignment is called, the final values in
+  // aa should increment by 2 rather than 1.
+  std::array<A, 3> aa       = {A{0}, A{0}, A{0}};
+  std::array<A, 3> expected = {A{0}, A{1}, A{2}};
+  std::ranges::iota(aa, A{0});
+  auto proj_val = [](DangerousCopyAssign const& el) { return el.val; };
+  assert(std::ranges::equal(aa, expected, std::ranges::equal_to{}, proj_val, proj_val));
+}
+
+constexpr bool test_results() {
+  // Tests on fundamental types
+  types::for_each(types::cpp17_input_iterator_list<int*>{}, []<class Iter> { test_results< Iter>(); });
+  test_results<cpp17_output_iterator<int*>>();
+  test_results<cpp20_output_iterator<int*>>();
+  test_results<int*, sized_sentinel<int*>>();
+
+  // Tests on non-fundamental types
+  test_user_defined_type();
+  test_dangerous_copy_assign();
+  return true;
+}
+
+int main(int, char**) {
+  test_results();
+  static_assert(test_results());
+  return 0;
+}

diff  --git a/libcxx/test/support/test_iterators.h b/libcxx/test/support/test_iterators.h
index ead8a3e8f87d2..702b82b9e15a7 100644
--- a/libcxx/test/support/test_iterators.h
+++ b/libcxx/test/support/test_iterators.h
@@ -1177,6 +1177,23 @@ rvalue_iterator(T*) -> rvalue_iterator<T>;
 
 static_assert(std::random_access_iterator<rvalue_iterator<int*>>);
 
+// The ProxyDiffTBase allows us to conditionally specify Proxy<T>::
diff erence_type
+// which we need in certain situations. For example when we want
+// std::weakly_incrementable<Proxy<T>> to be true.
+template <class T>
+struct ProxyDiffTBase {
+  // Add default `operator<=>` so that the derived type, Proxy, can also use the default `operator<=>`
+  friend constexpr auto operator<=>(const ProxyDiffTBase&, const ProxyDiffTBase&) = default;
+};
+
+template <class T>
+  requires requires { std::iter_
diff erence_t<T>{}; }
+struct ProxyDiffTBase<T> {
+  using 
diff erence_type = std::iter_
diff erence_t<T>;
+  // Add default `operator<=>` so that the derived type, Proxy, can also use the default `operator<=>`
+  friend constexpr auto operator<=>(const ProxyDiffTBase&, const ProxyDiffTBase&) = default;
+};
+
 // Proxy
 // ======================================================================
 // Proxy that can wrap a value or a reference. It simulates C++23's tuple
@@ -1187,6 +1204,7 @@ static_assert(std::random_access_iterator<rvalue_iterator<int*>>);
 // This class is useful for testing that if algorithms support proxy iterator
 // properly, i.e. calling ranges::iter_swap and ranges::iter_move instead of
 // plain swap and std::move.
+
 template <class T>
 struct Proxy;
 
@@ -1197,7 +1215,7 @@ template <class T>
 inline constexpr bool IsProxy<Proxy<T>> = true;
 
 template <class T>
-struct Proxy {
+struct Proxy : ProxyDiffTBase<T> {
   T data;
 
   constexpr T& getData() & { return data; }
@@ -1248,7 +1266,7 @@ struct Proxy {
 
   // Compare operators are defined for the convenience of the tests
   friend constexpr bool operator==(const Proxy&, const Proxy&)
-    requires (std::equality_comparable<T> && !std::is_reference_v<T>)
+    requires(std::equality_comparable<T> && !std::is_reference_v<T>)
   = default;
 
   // Helps compare e.g. `Proxy<int>` and `Proxy<int&>`. Note that the default equality comparison operator is deleted
@@ -1260,7 +1278,7 @@ struct Proxy {
   }
 
   friend constexpr auto operator<=>(const Proxy&, const Proxy&)
-    requires (std::three_way_comparable<T> && !std::is_reference_v<T>)
+    requires(std::three_way_comparable<T> && !std::is_reference_v<T>)
   = default;
 
   // Helps compare e.g. `Proxy<int>` and `Proxy<int&>`. Note that the default 3-way comparison operator is deleted when
@@ -1270,6 +1288,22 @@ struct Proxy {
     requires std::three_way_comparable_with<std::decay_t<T>, std::decay_t<U>> {
     return lhs.data <=> rhs.data;
   }
+
+  // Needed to allow certain types to be weakly_incrementable
+  constexpr Proxy& operator++()
+    requires(std::weakly_incrementable<std::remove_reference_t<T>>)
+  {
+    ++data;
+    return *this;
+  }
+
+  constexpr Proxy operator++(int)
+    requires(std::incrementable<std::remove_reference_t<T>>)
+  {
+    Proxy tmp = *this;
+    operator++();
+    return tmp;
+  }
 };
 
 // This is to make ProxyIterator model `std::indirectly_readable`

diff  --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 53252d5e2d673..febfb0f739e2c 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -1081,7 +1081,6 @@ def add_version_header(tc):
             "name": "__cpp_lib_ranges_iota",
             "values": {"c++23": 202202},
             "headers": ["numeric"],
-            "unimplemented": True,
         },
         {
             "name": "__cpp_lib_ranges_join_with",


        


More information about the libcxx-commits mailing list