[libc++][ranges] implement `ranges::elements_of` (#91414)

## Introduction

This patch implements `ranges::elements_of` from
[P2502R2](https://wg21.link/P2502R2). Specializations of `elements_of`
encapsulate a range and act as a tag in overload sets to disambiguate
when a range should be treated as a sequence rather than a single value.

```cpp
template <bool YieldElements>
std::generator<std::any> f(std::ranges::input_range auto &&r) {
  if constexpr (YieldElements) {
    co_yield std::ranges::elements_of(r);
  } else {
    co_yield r;
  }
}
```

## Reference

- [P2502R2: `std::generator`: Synchronous Coroutine Generator for
Ranges](https://wg21.link/P2502R2)
- [[range.elementsof]](https://eel.is/c++draft/range.elementsof)

Partially addresses #105226

---------

Co-authored-by: Louis Dionne <[email protected]>
Co-authored-by: A. Jiang <[email protected]>
NOKEYCHECK=True
GitOrigin-RevId: fa79e0a4001a18fd536ddfddcedc176efcf6a69c
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
index 1973201..396f425 100644
--- a/include/CMakeLists.txt
+++ b/include/CMakeLists.txt
@@ -711,6 +711,7 @@
   __ranges/data.h
   __ranges/drop_view.h
   __ranges/drop_while_view.h
+  __ranges/elements_of.h
   __ranges/elements_view.h
   __ranges/empty.h
   __ranges/empty_view.h
diff --git a/include/__ranges/elements_of.h b/include/__ranges/elements_of.h
new file mode 100644
index 0000000..3f89f49
--- /dev/null
+++ b/include/__ranges/elements_of.h
@@ -0,0 +1,49 @@
+// -*- 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___RANGES_ELEMENTS_OF_H
+#define _LIBCPP___RANGES_ELEMENTS_OF_H
+
+#include <__config>
+#include <__cstddef/byte.h>
+#include <__memory/allocator.h>
+#include <__ranges/concepts.h>
+#include <__utility/forward.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 <range _Range, class _Allocator = allocator<byte>>
+struct elements_of {
+  _LIBCPP_NO_UNIQUE_ADDRESS _Range range;
+  _LIBCPP_NO_UNIQUE_ADDRESS _Allocator allocator = _Allocator();
+};
+
+template <class _Range, class _Allocator = allocator<byte>>
+elements_of(_Range&&, _Allocator = _Allocator()) -> elements_of<_Range&&, _Allocator>;
+
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER >= 23
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___RANGES_ELEMENTS_OF_H
diff --git a/include/module.modulemap.in b/include/module.modulemap.in
index bbc2617..4d3fe48 100644
--- a/include/module.modulemap.in
+++ b/include/module.modulemap.in
@@ -1881,6 +1881,7 @@
       header "__ranges/drop_while_view.h"
       export std.functional.bind_back
     }
+    module elements_of                    { header "__ranges/elements_of.h" }
     module elements_view                  { header "__ranges/elements_view.h" }
     module empty                          { header "__ranges/empty.h" }
     module empty_view                     { header "__ranges/empty_view.h" }
diff --git a/include/ranges b/include/ranges
index cfaa66a..5723a34 100644
--- a/include/ranges
+++ b/include/ranges
@@ -116,6 +116,10 @@
   // [range.dangling], dangling iterator handling
   struct dangling;
 
+  // [range.elementsof], class template elements_of
+  template<range R, class Allocator = allocator<byte>>
+    struct elements_of;
+
   template<range R>
     using borrowed_iterator_t = see below;
 
@@ -419,6 +423,7 @@
 #    include <__ranges/data.h>
 #    include <__ranges/drop_view.h>
 #    include <__ranges/drop_while_view.h>
+#    include <__ranges/elements_of.h>
 #    include <__ranges/elements_view.h>
 #    include <__ranges/empty.h>
 #    include <__ranges/empty_view.h>
diff --git a/modules/std/ranges.inc b/modules/std/ranges.inc
index cc7daa3..99ce51f 100644
--- a/modules/std/ranges.inc
+++ b/modules/std/ranges.inc
@@ -83,8 +83,10 @@
     // [range.dangling], dangling iterator handling
     using std::ranges::dangling;
 
+#if _LIBCPP_STD_VER >= 23
     // [range.elementsof], class template elements_­of
-    // using std::ranges::elements_of;
+    using std::ranges::elements_of;
+#endif
 
     using std::ranges::borrowed_iterator_t;
 
diff --git a/test/std/ranges/range.utility/range.elementsof/ctad.pass.cpp b/test/std/ranges/range.utility/range.elementsof/ctad.pass.cpp
new file mode 100644
index 0000000..532fe01
--- /dev/null
+++ b/test/std/ranges/range.utility/range.elementsof/ctad.pass.cpp
@@ -0,0 +1,100 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <ranges>
+//
+// template<range R, class Allocator = allocator<byte>>
+// struct std::ranges::elements_of;
+//
+// template<class R, class Allocator = allocator<byte>>
+// elements_of(R&&, Allocator = Allocator()) -> elements_of<R&&, Allocator>;
+
+#include <concepts>
+#include <cstddef>
+#include <memory>
+#include <ranges>
+#include <utility>
+
+#include "min_allocator.h"
+#include "test_allocator.h"
+#include "test_iterators.h"
+
+template <class Allocator, class Range>
+constexpr void test_impl() {
+  Allocator a;
+
+  // With a lvalue range
+  {
+    Range r;
+    std::ranges::elements_of elements(r);
+    static_assert(std::same_as<decltype(elements), std::ranges::elements_of<Range&, std::allocator<std::byte>>>);
+  }
+
+  // With a rvalue range
+  {
+    Range r;
+    std::ranges::elements_of elements(std::move(r));
+    static_assert(std::same_as<decltype(elements), std::ranges::elements_of<Range&&, std::allocator<std::byte>>>);
+  }
+
+  // With lvalue range and allocator
+  {
+    Range r;
+    std::ranges::elements_of elements(r, a);
+    static_assert(std::same_as<decltype(elements), std::ranges::elements_of<Range&, Allocator>>);
+  }
+
+  // With rvalue range and allocator
+  {
+    Range r;
+    std::ranges::elements_of elements(std::move(r), Allocator());
+    static_assert(std::same_as<decltype(elements), std::ranges::elements_of<Range&&, Allocator>>);
+  }
+
+  // Ensure we can use designated initializers
+  {
+    // lvalues
+    {
+      Range r;
+      std::ranges::elements_of elements{.range = r, .allocator = a};
+      static_assert(std::same_as<decltype(elements), std::ranges::elements_of<Range&, Allocator>>);
+    }
+
+    // rvalues
+    {
+      Range r;
+      std::ranges::elements_of elements{.range = std::move(r), .allocator = Allocator()};
+      static_assert(std::same_as<decltype(elements), std::ranges::elements_of<Range&&, Allocator>>);
+    }
+  }
+}
+
+template <class Iterator>
+struct Range {
+  Iterator begin() const;
+  sentinel_wrapper<Iterator> end() const;
+};
+
+constexpr bool test() {
+  types::for_each(types::type_list<std::allocator<std::byte>, min_allocator<std::byte>, test_allocator<std::byte>>{},
+                  []<class Allocator> {
+                    types::for_each(types::cpp20_input_iterator_list<int*>{}, []<class Iterator> {
+                      test_impl<Allocator, Range<Iterator>>();
+                    });
+                  });
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/test/std/ranges/range.utility/range.elementsof/elements_of.pass.cpp b/test/std/ranges/range.utility/range.elementsof/elements_of.pass.cpp
new file mode 100644
index 0000000..77a9b84
--- /dev/null
+++ b/test/std/ranges/range.utility/range.elementsof/elements_of.pass.cpp
@@ -0,0 +1,106 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <ranges>
+//
+// template<range R, class Allocator = allocator<byte>>
+// struct std::ranges::elements_of;
+
+#include <cassert>
+#include <concepts>
+#include <memory>
+#include <ranges>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "min_allocator.h"
+#include "test_allocator.h"
+#include "test_iterators.h"
+
+template <class Iterator>
+struct Range {
+  using Sentinel = sentinel_wrapper<Iterator>;
+
+  constexpr Iterator begin() { return Iterator(data_.data()); }
+
+  constexpr Sentinel end() { return Sentinel(Iterator(data_.data() + data_.size())); }
+
+private:
+  std::vector<int> data_ = {0, 1, 2, 3};
+};
+
+template <class Range, class Allocator>
+constexpr bool test_range() {
+  Range r;
+
+  using elements_of_t = std::ranges::elements_of<Range&, Allocator>;
+  {
+    // constructor
+    std::same_as<elements_of_t> decltype(auto) e = std::ranges::elements_of(r, Allocator());
+    std::same_as<Range&> decltype(auto) range    = e.range;
+    assert(std::ranges::distance(range) == 4);
+    [[maybe_unused]] std::same_as<Allocator> decltype(auto) allocator = e.allocator;
+  }
+  {
+    // designated initializer
+    std::same_as<elements_of_t> decltype(auto) e = std::ranges::elements_of{
+        .range     = r,
+        .allocator = Allocator(),
+    };
+    std::same_as<Range&> decltype(auto) range = e.range;
+    assert(&range == &r);
+    assert(std::ranges::distance(range) == 4);
+    [[maybe_unused]] std::same_as<Allocator> decltype(auto) allocator = e.allocator;
+  }
+  {
+    // copy constructor
+    std::same_as<elements_of_t> decltype(auto) e   = std::ranges::elements_of(r, Allocator());
+    std::same_as<elements_of_t> auto copy          = e;
+    std::same_as<Range&> decltype(auto) range      = e.range;
+    std::same_as<Range&> decltype(auto) copy_range = copy.range;
+    assert(&range == &r);
+    assert(&range == &copy_range);
+    assert(std::ranges::distance(range) == 4);
+    [[maybe_unused]] std::same_as<Allocator> decltype(auto) copy_allocator = copy.allocator;
+  }
+
+  using elements_of_r_t = std::ranges::elements_of<Range&&, Allocator>;
+  {
+    // move constructor
+    std::same_as<elements_of_r_t> decltype(auto) e  = std::ranges::elements_of(std::move(r), Allocator());
+    std::same_as<elements_of_r_t> auto copy         = std::move(e);
+    std::same_as<Range&&> decltype(auto) range      = std::move(e.range);
+    std::same_as<Range&&> decltype(auto) copy_range = std::move(copy.range);
+    assert(&range == &r);
+    assert(&range == &copy_range);
+    assert(std::ranges::distance(range) == 4);
+    [[maybe_unused]] std::same_as<Allocator> decltype(auto) copy_allocator = copy.allocator;
+  }
+  return true;
+}
+
+constexpr bool test() {
+  types::for_each(types::type_list<std::allocator<std::byte>, min_allocator<std::byte>, test_allocator<std::byte>>{},
+                  []<class Allocator> {
+                    types::for_each(types::cpp20_input_iterator_list<int*>{}, []<class Iterator> {
+                      test_range<Range<Iterator>, Allocator>();
+                    });
+                  });
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}