Skip to content

Commit fa79e0a

Browse files
xiaoyang-sdeldionnefrederick-vs-ja
authored
[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]>
1 parent d8b03f2 commit fa79e0a

File tree

7 files changed

+265
-1
lines changed

7 files changed

+265
-1
lines changed

libcxx/include/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,7 @@ set(files
711711
__ranges/data.h
712712
__ranges/drop_view.h
713713
__ranges/drop_while_view.h
714+
__ranges/elements_of.h
714715
__ranges/elements_view.h
715716
__ranges/empty.h
716717
__ranges/empty_view.h
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// -*- C++ -*-
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#ifndef _LIBCPP___RANGES_ELEMENTS_OF_H
11+
#define _LIBCPP___RANGES_ELEMENTS_OF_H
12+
13+
#include <__config>
14+
#include <__cstddef/byte.h>
15+
#include <__memory/allocator.h>
16+
#include <__ranges/concepts.h>
17+
#include <__utility/forward.h>
18+
19+
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
20+
# pragma GCC system_header
21+
#endif
22+
23+
_LIBCPP_PUSH_MACROS
24+
#include <__undef_macros>
25+
26+
_LIBCPP_BEGIN_NAMESPACE_STD
27+
28+
#if _LIBCPP_STD_VER >= 23
29+
30+
namespace ranges {
31+
32+
template <range _Range, class _Allocator = allocator<byte>>
33+
struct elements_of {
34+
_LIBCPP_NO_UNIQUE_ADDRESS _Range range;
35+
_LIBCPP_NO_UNIQUE_ADDRESS _Allocator allocator = _Allocator();
36+
};
37+
38+
template <class _Range, class _Allocator = allocator<byte>>
39+
elements_of(_Range&&, _Allocator = _Allocator()) -> elements_of<_Range&&, _Allocator>;
40+
41+
} // namespace ranges
42+
43+
#endif // _LIBCPP_STD_VER >= 23
44+
45+
_LIBCPP_END_NAMESPACE_STD
46+
47+
_LIBCPP_POP_MACROS
48+
49+
#endif // _LIBCPP___RANGES_ELEMENTS_OF_H

libcxx/include/module.modulemap.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1881,6 +1881,7 @@ module std [system] {
18811881
header "__ranges/drop_while_view.h"
18821882
export std.functional.bind_back
18831883
}
1884+
module elements_of { header "__ranges/elements_of.h" }
18841885
module elements_view { header "__ranges/elements_view.h" }
18851886
module empty { header "__ranges/empty.h" }
18861887
module empty_view { header "__ranges/empty_view.h" }

libcxx/include/ranges

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ namespace std::ranges {
116116
// [range.dangling], dangling iterator handling
117117
struct dangling;
118118
119+
// [range.elementsof], class template elements_of
120+
template<range R, class Allocator = allocator<byte>>
121+
struct elements_of;
122+
119123
template<range R>
120124
using borrowed_iterator_t = see below;
121125
@@ -419,6 +423,7 @@ namespace std {
419423
# include <__ranges/data.h>
420424
# include <__ranges/drop_view.h>
421425
# include <__ranges/drop_while_view.h>
426+
# include <__ranges/elements_of.h>
422427
# include <__ranges/elements_view.h>
423428
# include <__ranges/empty.h>
424429
# include <__ranges/empty_view.h>

libcxx/modules/std/ranges.inc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,10 @@ export namespace std {
8383
// [range.dangling], dangling iterator handling
8484
using std::ranges::dangling;
8585

86+
#if _LIBCPP_STD_VER >= 23
8687
// [range.elementsof], class template elements_­of
87-
// using std::ranges::elements_of;
88+
using std::ranges::elements_of;
89+
#endif
8890

8991
using std::ranges::borrowed_iterator_t;
9092

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
10+
11+
// <ranges>
12+
//
13+
// template<range R, class Allocator = allocator<byte>>
14+
// struct std::ranges::elements_of;
15+
//
16+
// template<class R, class Allocator = allocator<byte>>
17+
// elements_of(R&&, Allocator = Allocator()) -> elements_of<R&&, Allocator>;
18+
19+
#include <concepts>
20+
#include <cstddef>
21+
#include <memory>
22+
#include <ranges>
23+
#include <utility>
24+
25+
#include "min_allocator.h"
26+
#include "test_allocator.h"
27+
#include "test_iterators.h"
28+
29+
template <class Allocator, class Range>
30+
constexpr void test_impl() {
31+
Allocator a;
32+
33+
// With a lvalue range
34+
{
35+
Range r;
36+
std::ranges::elements_of elements(r);
37+
static_assert(std::same_as<decltype(elements), std::ranges::elements_of<Range&, std::allocator<std::byte>>>);
38+
}
39+
40+
// With a rvalue range
41+
{
42+
Range r;
43+
std::ranges::elements_of elements(std::move(r));
44+
static_assert(std::same_as<decltype(elements), std::ranges::elements_of<Range&&, std::allocator<std::byte>>>);
45+
}
46+
47+
// With lvalue range and allocator
48+
{
49+
Range r;
50+
std::ranges::elements_of elements(r, a);
51+
static_assert(std::same_as<decltype(elements), std::ranges::elements_of<Range&, Allocator>>);
52+
}
53+
54+
// With rvalue range and allocator
55+
{
56+
Range r;
57+
std::ranges::elements_of elements(std::move(r), Allocator());
58+
static_assert(std::same_as<decltype(elements), std::ranges::elements_of<Range&&, Allocator>>);
59+
}
60+
61+
// Ensure we can use designated initializers
62+
{
63+
// lvalues
64+
{
65+
Range r;
66+
std::ranges::elements_of elements{.range = r, .allocator = a};
67+
static_assert(std::same_as<decltype(elements), std::ranges::elements_of<Range&, Allocator>>);
68+
}
69+
70+
// rvalues
71+
{
72+
Range r;
73+
std::ranges::elements_of elements{.range = std::move(r), .allocator = Allocator()};
74+
static_assert(std::same_as<decltype(elements), std::ranges::elements_of<Range&&, Allocator>>);
75+
}
76+
}
77+
}
78+
79+
template <class Iterator>
80+
struct Range {
81+
Iterator begin() const;
82+
sentinel_wrapper<Iterator> end() const;
83+
};
84+
85+
constexpr bool test() {
86+
types::for_each(types::type_list<std::allocator<std::byte>, min_allocator<std::byte>, test_allocator<std::byte>>{},
87+
[]<class Allocator> {
88+
types::for_each(types::cpp20_input_iterator_list<int*>{}, []<class Iterator> {
89+
test_impl<Allocator, Range<Iterator>>();
90+
});
91+
});
92+
93+
return true;
94+
}
95+
96+
int main(int, char**) {
97+
test();
98+
static_assert(test());
99+
return 0;
100+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
10+
11+
// <ranges>
12+
//
13+
// template<range R, class Allocator = allocator<byte>>
14+
// struct std::ranges::elements_of;
15+
16+
#include <cassert>
17+
#include <concepts>
18+
#include <memory>
19+
#include <ranges>
20+
#include <type_traits>
21+
#include <utility>
22+
#include <vector>
23+
24+
#include "min_allocator.h"
25+
#include "test_allocator.h"
26+
#include "test_iterators.h"
27+
28+
template <class Iterator>
29+
struct Range {
30+
using Sentinel = sentinel_wrapper<Iterator>;
31+
32+
constexpr Iterator begin() { return Iterator(data_.data()); }
33+
34+
constexpr Sentinel end() { return Sentinel(Iterator(data_.data() + data_.size())); }
35+
36+
private:
37+
std::vector<int> data_ = {0, 1, 2, 3};
38+
};
39+
40+
template <class Range, class Allocator>
41+
constexpr bool test_range() {
42+
Range r;
43+
44+
using elements_of_t = std::ranges::elements_of<Range&, Allocator>;
45+
{
46+
// constructor
47+
std::same_as<elements_of_t> decltype(auto) e = std::ranges::elements_of(r, Allocator());
48+
std::same_as<Range&> decltype(auto) range = e.range;
49+
assert(std::ranges::distance(range) == 4);
50+
[[maybe_unused]] std::same_as<Allocator> decltype(auto) allocator = e.allocator;
51+
}
52+
{
53+
// designated initializer
54+
std::same_as<elements_of_t> decltype(auto) e = std::ranges::elements_of{
55+
.range = r,
56+
.allocator = Allocator(),
57+
};
58+
std::same_as<Range&> decltype(auto) range = e.range;
59+
assert(&range == &r);
60+
assert(std::ranges::distance(range) == 4);
61+
[[maybe_unused]] std::same_as<Allocator> decltype(auto) allocator = e.allocator;
62+
}
63+
{
64+
// copy constructor
65+
std::same_as<elements_of_t> decltype(auto) e = std::ranges::elements_of(r, Allocator());
66+
std::same_as<elements_of_t> auto copy = e;
67+
std::same_as<Range&> decltype(auto) range = e.range;
68+
std::same_as<Range&> decltype(auto) copy_range = copy.range;
69+
assert(&range == &r);
70+
assert(&range == &copy_range);
71+
assert(std::ranges::distance(range) == 4);
72+
[[maybe_unused]] std::same_as<Allocator> decltype(auto) copy_allocator = copy.allocator;
73+
}
74+
75+
using elements_of_r_t = std::ranges::elements_of<Range&&, Allocator>;
76+
{
77+
// move constructor
78+
std::same_as<elements_of_r_t> decltype(auto) e = std::ranges::elements_of(std::move(r), Allocator());
79+
std::same_as<elements_of_r_t> auto copy = std::move(e);
80+
std::same_as<Range&&> decltype(auto) range = std::move(e.range);
81+
std::same_as<Range&&> decltype(auto) copy_range = std::move(copy.range);
82+
assert(&range == &r);
83+
assert(&range == &copy_range);
84+
assert(std::ranges::distance(range) == 4);
85+
[[maybe_unused]] std::same_as<Allocator> decltype(auto) copy_allocator = copy.allocator;
86+
}
87+
return true;
88+
}
89+
90+
constexpr bool test() {
91+
types::for_each(types::type_list<std::allocator<std::byte>, min_allocator<std::byte>, test_allocator<std::byte>>{},
92+
[]<class Allocator> {
93+
types::for_each(types::cpp20_input_iterator_list<int*>{}, []<class Iterator> {
94+
test_range<Range<Iterator>, Allocator>();
95+
});
96+
});
97+
98+
return true;
99+
}
100+
101+
int main(int, char**) {
102+
test();
103+
static_assert(test());
104+
105+
return 0;
106+
}

0 commit comments

Comments
 (0)