Skip to content

Commit bf71a32

Browse files
committed
[feature] radr::counted and radr::counted_sp
1 parent 0327196 commit bf71a32

File tree

5 files changed

+323
-1
lines changed

5 files changed

+323
-1
lines changed

docs/implementation_status.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ We plan to add equivalent objects for all standard library adaptors.
3535
| `radr::reverse` | C++20 | | `std::views::reverse` | C++20 | |
3636
| `radr::slice(m, n)` | C++20 | | *not yet available* | | get subrange between m and n |
3737
| `radr::split(pat)` | C++20 | | `std::views::split` | C++20 | |
38+
| *not planned* | C++20 | | `std::views::lazy_split` | C++20 | use `radr::to_single_pass | radr::split` |
3839
| `radr::take(n)` | C++20 | | `std::views::take` | C++20 | |
3940
| `radr::take_exactly(n)` | C++20 | | *not yet available* | | turns unsized into sized |
4041
| `radr::take_while(fn)` | C++20 | | `std::views::take_while` | C++20 | |
@@ -52,6 +53,8 @@ All range adaptors from this library are available in C++20, although `radr::as_
5253

5354
| Range factories (objects) | C++XY | | Equivalent in `std::` | C++XY | Remarks |
5455
|-------------------------------|-------|-|-------------------------|-----------|-------------------------------------------|
56+
| `radr::counted(it, n)` | C++20 | | `std::views::counted` | C++20 | multi-pass version of counted |
57+
| `radr::counted_sp(it, n)` | C++20 | | `std::views::counted` | C++20 | single-pass version of counted |
5558
| `radr::empty<T>` | C++20 | | `std::views::empty` | C++20 | |
5659
| `radr::iota(val[, bound])` | C++20 | | `std::views::iota` | C++20 | multi-pass version of iota |
5760
| `radr::iota_sp(val[, bound])` | C++20 | | `std::views::iota` | C++20 | single-pass version of iota |

include/radr/factory/counted.hpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// -*- C++ -*-
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// Copyright (c) 2023-2025 Hannes Hauswedell
5+
//
6+
// Licensed under the Apache License v2.0 with LLVM Exceptions.
7+
// See the LICENSE file for details.
8+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#pragma once
13+
14+
#include <iterator>
15+
16+
#include "../rad/take.hpp"
17+
18+
namespace radr
19+
{
20+
21+
inline namespace cpo
22+
{
23+
24+
/*!\brief Create a range from an iterator and a count.
25+
* \tparam It The type of \p it; required to model std::forward_iterator.
26+
* \param[in] it The iterator.
27+
* \param[in] n The count.
28+
* \details
29+
*
30+
* In contrast to std::views::counted, this factory always returns a multi-pass range and thus requires
31+
* `std::forward_iterator<It>` and not just `std::input_or_output_iterator<It>`.
32+
*
33+
* There is radr::counted_sp which is always a single-pass range and does not have this requirement.
34+
*
35+
* Unlike std::views::counted, we consider radr::counted a range factory and not a range adaptor, because
36+
* you cannot pipe a range into it.
37+
*
38+
* ### Safety
39+
*
40+
* Note that this function (like std::views::counted) performs no bounds-checking.
41+
*/
42+
inline constexpr auto counted = []<std::forward_iterator It>(It it, size_t const n)
43+
{
44+
/* this is a bit of a hack; the sentinels are irrelevant and also the first size parameter
45+
* but take_borrow will dispatch to either subborrow (for ra-iter) or else
46+
* (std::counted_iterator, default_sentinel) which is what we want.
47+
*/
48+
return detail::take_borrow(
49+
borrowing_rad{it, std::unreachable_sentinel, make_const_iterator(it), std::unreachable_sentinel, n},
50+
n);
51+
};
52+
53+
/*!\brief Single-pass version of radr::counted.
54+
* \tparam It The type of \p it; required to model std::input_or_output_iterator.
55+
* \param[in,out] it The iterator.
56+
* \param[in] n The count.
57+
* \details
58+
*
59+
* This factory always returns a radr::generator, a move-only, single-pass range.
60+
*
61+
* The requirements on the iterator type are weaker than for radr::counted.
62+
*
63+
* ### Safety
64+
*
65+
* Note that this function (like std::views::counted) performs no bounds-checking.
66+
*/
67+
inline constexpr auto counted_sp = []<typename It>(It && it, size_t const n)
68+
{
69+
static_assert(std::input_or_output_iterator<std::remove_cvref_t<It>>,
70+
"You must pass an iterator as first argument to radr::counted_sp().");
71+
72+
// Intentionally, no forwarding reference in this signature
73+
return [](It it, size_t const n) -> generator<std::iter_reference_t<It>, std::iter_value_t<It>>
74+
{
75+
uint64_t i = 0;
76+
while (i < n)
77+
{
78+
co_yield *it;
79+
++i;
80+
++it;
81+
}
82+
}(std::forward<It>(it), n);
83+
};
84+
85+
} // namespace cpo
86+
} // namespace radr

include/radr/rad_util/borrowing_rad.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class borrowing_rad : public rad_interface<borrowing_rad<Iter, Sent, CIter, CSen
8888
}
8989

9090
public:
91-
borrowing_rad() = default;
91+
constexpr borrowing_rad() = default;
9292

9393
/*!\name Constructors: Iterator, Sentinel
9494
* \{

tests/unit/factory/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
radr_unit_test(counted)
12
radr_unit_test(empty)
23
radr_unit_test(iota)
34
radr_unit_test(istream)

tests/unit/factory/counted.cpp

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
#include <array>
2+
#include <deque>
3+
#include <forward_list>
4+
#include <iterator>
5+
#include <list>
6+
#include <ranges>
7+
#include <vector>
8+
9+
#include <gtest/gtest.h>
10+
#include <radr/test/gtest_helpers.hpp>
11+
12+
#include <radr/factory/counted.hpp>
13+
#include <radr/factory/iota.hpp>
14+
15+
TEST(counted, Basic)
16+
{
17+
int arr[]{1, 2, 3, 4, 5};
18+
auto rng = radr::counted(arr, 3);
19+
std::vector<int> expected{1, 2, 3};
20+
EXPECT_RANGE_EQ(rng, expected);
21+
}
22+
23+
TEST(counted, EdgeCases_ZeroCount)
24+
{
25+
int arr[]{1, 2, 3};
26+
auto rng = radr::counted(arr, 0);
27+
std::vector<int> expected{};
28+
EXPECT_RANGE_EQ(rng, expected);
29+
}
30+
31+
TEST(counted, EdgeCases_ExactEnd)
32+
{
33+
int arr[]{7, 8, 9, 10};
34+
auto rng = radr::counted(arr + 2, 2);
35+
std::vector<int> expected{9, 10};
36+
EXPECT_RANGE_EQ(rng, expected);
37+
}
38+
39+
TEST(counted, ForwardIterator)
40+
{
41+
std::forward_list<int> flist{5, 6, 7, 8};
42+
auto it = flist.begin();
43+
std::ranges::advance(it, 1);
44+
45+
auto rng = radr::counted(it, 2);
46+
std::vector<int> expected{6, 7};
47+
48+
EXPECT_RANGE_EQ(rng, expected);
49+
50+
EXPECT_TRUE(std::regular<decltype(rng)>);
51+
EXPECT_TRUE(radr::mp_range<decltype(rng)>);
52+
EXPECT_TRUE(std::ranges::sized_range<decltype(rng)>);
53+
EXPECT_TRUE(std::ranges::borrowed_range<decltype(rng)>);
54+
55+
EXPECT_FALSE(std::ranges::bidirectional_range<decltype(rng)>);
56+
EXPECT_FALSE(std::ranges::random_access_range<decltype(rng)>);
57+
EXPECT_FALSE(std::ranges::contiguous_range<decltype(rng)>);
58+
59+
EXPECT_FALSE(radr::common_range<decltype(rng)>);
60+
}
61+
62+
TEST(counted, BidirectionalIterator)
63+
{
64+
std::list<int> lst{11, 12, 13, 14, 15};
65+
auto it = lst.begin();
66+
std::ranges::advance(it, 1);
67+
68+
auto rng = radr::counted(it, 3);
69+
std::vector<int> expected{12, 13, 14};
70+
71+
EXPECT_RANGE_EQ(rng, expected);
72+
73+
EXPECT_TRUE(std::regular<decltype(rng)>);
74+
EXPECT_TRUE(radr::mp_range<decltype(rng)>);
75+
EXPECT_TRUE(std::ranges::sized_range<decltype(rng)>);
76+
EXPECT_TRUE(std::ranges::borrowed_range<decltype(rng)>);
77+
78+
EXPECT_TRUE(std::ranges::bidirectional_range<decltype(rng)>);
79+
EXPECT_FALSE(std::ranges::random_access_range<decltype(rng)>);
80+
EXPECT_FALSE(std::ranges::contiguous_range<decltype(rng)>);
81+
82+
EXPECT_FALSE(radr::common_range<decltype(rng)>);
83+
}
84+
85+
TEST(counted, RandomAccessIterator)
86+
{
87+
std::deque<int> vec{3, 1, 4, 1, 5};
88+
auto rng = radr::counted(vec.begin() + 2, 2);
89+
std::vector<int> expected{4, 1};
90+
EXPECT_RANGE_EQ(rng, expected);
91+
92+
EXPECT_TRUE(std::regular<decltype(rng)>);
93+
EXPECT_TRUE(radr::mp_range<decltype(rng)>);
94+
EXPECT_TRUE(std::ranges::sized_range<decltype(rng)>);
95+
EXPECT_TRUE(std::ranges::borrowed_range<decltype(rng)>);
96+
97+
EXPECT_TRUE(std::ranges::bidirectional_range<decltype(rng)>);
98+
EXPECT_TRUE(std::ranges::random_access_range<decltype(rng)>);
99+
EXPECT_FALSE(std::ranges::contiguous_range<decltype(rng)>);
100+
101+
EXPECT_TRUE(radr::common_range<decltype(rng)>);
102+
}
103+
104+
TEST(counted, ContiguousIterator)
105+
{
106+
std::vector<int> vec{3, 1, 4, 1, 5};
107+
auto rng = radr::counted(vec.begin() + 2, 2);
108+
std::vector<int> expected{4, 1};
109+
EXPECT_RANGE_EQ(rng, expected);
110+
111+
EXPECT_TRUE(std::regular<decltype(rng)>);
112+
EXPECT_TRUE(radr::mp_range<decltype(rng)>);
113+
EXPECT_TRUE(std::ranges::sized_range<decltype(rng)>);
114+
EXPECT_TRUE(std::ranges::borrowed_range<decltype(rng)>);
115+
116+
EXPECT_TRUE(std::ranges::bidirectional_range<decltype(rng)>);
117+
EXPECT_TRUE(std::ranges::random_access_range<decltype(rng)>);
118+
EXPECT_TRUE(std::ranges::contiguous_range<decltype(rng)>);
119+
120+
EXPECT_TRUE(radr::common_range<decltype(rng)>);
121+
}
122+
123+
TEST(counted, Constexpr)
124+
{
125+
static constexpr std::array<int, 4> a{5, 6, 7, 8};
126+
constexpr auto rng = radr::counted(a.begin() + 1, 2);
127+
128+
static_assert(rng.size() == 2);
129+
static_assert(*rng.begin() == 6);
130+
}
131+
132+
TEST(counted, Constexpr_Zero)
133+
{
134+
static constexpr std::array<int, 1> a{1};
135+
constexpr auto rng = radr::counted(a.begin(), 0);
136+
137+
static_assert(rng.size() == 0);
138+
static_assert(rng.begin() == rng.end());
139+
}
140+
141+
//===========================================================================
142+
// single-pass version
143+
//===========================================================================
144+
145+
TEST(counted_sp, Basic)
146+
{
147+
int arr[]{1, 2, 3, 4, 5};
148+
auto rng = radr::counted_sp(&*arr, 3);
149+
std::vector<int> expected{1, 2, 3};
150+
EXPECT_RANGE_EQ(rng, expected);
151+
}
152+
153+
TEST(counted_sp, EdgeCases_ZeroCount)
154+
{
155+
int arr[]{1, 2, 3};
156+
auto rng = radr::counted_sp(&*arr, 0);
157+
std::vector<int> expected{};
158+
EXPECT_RANGE_EQ(rng, expected);
159+
}
160+
161+
TEST(counted_sp, EdgeCases_ExactEnd)
162+
{
163+
int arr[]{7, 8, 9, 10};
164+
auto rng = radr::counted_sp(&*arr + 2, 2);
165+
std::vector<int> expected{9, 10};
166+
EXPECT_RANGE_EQ(rng, expected);
167+
}
168+
169+
TEST(counted_sp, InputIterator)
170+
{
171+
auto gen = radr::iota_sp(5, 9);
172+
auto it = gen.begin();
173+
std::ranges::advance(it, 1);
174+
175+
auto rng = radr::counted_sp(it, 2);
176+
std::vector<int> expected{6, 7};
177+
178+
EXPECT_RANGE_EQ(rng, expected);
179+
#ifdef __cpp_lib_generator
180+
EXPECT_SAME_TYPE(decltype(rng), (radr::generator<int &&, int, void>));
181+
#else
182+
EXPECT_SAME_TYPE(decltype(rng), (radr::generator<int>));
183+
#endif
184+
}
185+
186+
TEST(counted_sp, ForwardIterator)
187+
{
188+
std::forward_list<int> flist{5, 6, 7, 8};
189+
auto it = flist.begin();
190+
std::ranges::advance(it, 1);
191+
192+
auto rng = radr::counted_sp(it, 2);
193+
std::vector<int> expected{6, 7};
194+
195+
EXPECT_RANGE_EQ(rng, expected);
196+
EXPECT_SAME_TYPE(decltype(rng), (radr::generator<int &, int>));
197+
}
198+
199+
TEST(counted_sp, BidirectionalIterator)
200+
{
201+
std::list<int> lst{11, 12, 13, 14, 15};
202+
auto it = lst.begin();
203+
std::ranges::advance(it, 1);
204+
205+
auto rng = radr::counted_sp(it, 3);
206+
std::vector<int> expected{12, 13, 14};
207+
208+
EXPECT_RANGE_EQ(rng, expected);
209+
EXPECT_SAME_TYPE(decltype(rng), (radr::generator<int &, int>));
210+
}
211+
212+
TEST(counted_sp, RandomAccessIterator)
213+
{
214+
std::deque<int> vec{3, 1, 4, 1, 5};
215+
auto rng = radr::counted_sp(vec.begin() + 2, 2);
216+
std::vector<int> expected{4, 1};
217+
218+
EXPECT_RANGE_EQ(rng, expected);
219+
EXPECT_SAME_TYPE(decltype(rng), (radr::generator<int &, int>));
220+
}
221+
222+
TEST(counted_sp, ContiguousIterator)
223+
{
224+
std::vector<int> vec{3, 1, 4, 1, 5};
225+
auto rng = radr::counted_sp(vec.begin() + 2, 2);
226+
std::vector<int> expected{4, 1};
227+
228+
EXPECT_RANGE_EQ(rng, expected);
229+
EXPECT_SAME_TYPE(decltype(rng), (radr::generator<int &, int>));
230+
}
231+
232+
// generator not yet constexpr

0 commit comments

Comments
 (0)