Skip to content

Commit f3c56ef

Browse files
committed
[feature] radr::iota and radr::iota_sp
1 parent 688e11c commit f3c56ef

File tree

4 files changed

+572
-1
lines changed

4 files changed

+572
-1
lines changed

docs/implementation_status.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,11 @@ All range adaptors from this library are available in C++20, although `radr::as_
5151
| Range factories | Equivalent in `std::` | Remarks |
5252
|-------------------------------|-------------------------|------------------------------------------------------|
5353
| `radr::empty<T>` | `std::views::empty` | |
54+
| `radr::iota(val[, bound])` | `std::views::iota` | multi-pass version of iota |
55+
| `radr::iota_sp(val[, bound])` | `std::views::iota` | single-pass version of iota |
5456
| `radr::istream<Val>` | `std::views::istream` | |
55-
| `radr::repeat(val, bound)` | `std::views::repeat` | allows indirect storage and static bounds |
57+
| `radr::istream<Val>` | `std::views::istream` | |
58+
| `radr::repeat(val[, bound])` | `std::views::repeat` | allows indirect storage and static bounds |
5659
| `radr::single(val)` | `std::views::single` | allows indirect storage |
5760

5861
## Notable functions

include/radr/factory/iota.hpp

Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
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 <concepts>
15+
#include <istream>
16+
#include <iterator>
17+
18+
#include "radr/concepts.hpp"
19+
#include "radr/detail/fwd.hpp"
20+
#include "radr/generator.hpp"
21+
#include "radr/rad_util/borrowing_rad.hpp"
22+
23+
namespace radr::detail
24+
{
25+
26+
// clang-format off
27+
template <typename Int>
28+
using wider_signed_t = std::conditional_t<sizeof(Int) < sizeof(short), short,
29+
std::conditional_t<sizeof(Int) < sizeof(int), int,
30+
std::conditional_t<sizeof(Int) < sizeof(long), long,
31+
long long>>>;
32+
// clang-format on
33+
34+
// iota_diff_t
35+
template <class Start>
36+
using iota_diff_t =
37+
std::conditional_t<(!std::is_integral_v<Start> || sizeof(std::iter_difference_t<Start>) > sizeof(Start)),
38+
std::iter_difference_t<Start>,
39+
wider_signed_t<Start>>;
40+
41+
// __decrementable, __advanceable
42+
template <class Iter>
43+
concept decrementable = std::incrementable<Iter> && requires(Iter i) {
44+
{
45+
--i
46+
} -> std::same_as<Iter &>;
47+
{
48+
i--
49+
} -> std::same_as<Iter>;
50+
};
51+
52+
template <class Iter>
53+
concept advanceable =
54+
decrementable<Iter> && std::totally_ordered<Iter> && requires(Iter i, Iter const j, iota_diff_t<Iter> const n) {
55+
{
56+
i += n
57+
} -> std::same_as<Iter &>;
58+
{
59+
i -= n
60+
} -> std::same_as<Iter &>;
61+
Iter(j + n);
62+
Iter(n + j);
63+
Iter(j - n);
64+
{
65+
j - j
66+
} -> std::convertible_to<iota_diff_t<Iter>>;
67+
};
68+
69+
// Iterator
70+
template <std::incrementable Start>
71+
class iota_iterator
72+
{
73+
Start value_{};
74+
75+
public:
76+
using value_type = Start;
77+
using difference_type = iota_diff_t<value_type>;
78+
79+
// clang-format off
80+
using iterator_concept =
81+
std::conditional_t<advanceable<value_type>, std::random_access_iterator_tag,
82+
std::conditional_t<decrementable<value_type>, std::bidirectional_iterator_tag,
83+
std::forward_iterator_tag>>;
84+
// clang-format on
85+
86+
constexpr iota_iterator() = default;
87+
constexpr explicit iota_iterator(Start value) : value_(std::move(value)) {}
88+
89+
constexpr value_type operator*() const noexcept(std::is_nothrow_copy_constructible_v<value_type>) { return value_; }
90+
constexpr iota_iterator & operator++()
91+
{
92+
++value_;
93+
return *this;
94+
}
95+
constexpr iota_iterator operator++(int)
96+
{
97+
auto tmp = *this;
98+
++*this;
99+
return tmp;
100+
}
101+
102+
constexpr iota_iterator & operator--()
103+
requires decrementable<value_type>
104+
{
105+
--value_;
106+
return *this;
107+
}
108+
constexpr iota_iterator operator--(int)
109+
requires decrementable<value_type>
110+
{
111+
auto tmp = *this;
112+
--*this;
113+
return tmp;
114+
}
115+
116+
constexpr iota_iterator & operator+=(difference_type n)
117+
requires advanceable<value_type>
118+
{
119+
if constexpr (std::is_integral_v<value_type> && !std::is_signed_v<value_type>)
120+
{
121+
if (n >= difference_type(0))
122+
value_ += static_cast<value_type>(n);
123+
else
124+
value_ -= static_cast<value_type>(-n);
125+
}
126+
else
127+
{
128+
value_ += n;
129+
}
130+
return *this;
131+
}
132+
133+
constexpr iota_iterator & operator-=(difference_type n)
134+
requires advanceable<value_type>
135+
{
136+
if constexpr (std::is_integral_v<value_type> && !std::is_signed_v<value_type>)
137+
{
138+
if (n >= difference_type(0))
139+
value_ -= static_cast<value_type>(n);
140+
else
141+
value_ += static_cast<value_type>(-n);
142+
}
143+
else
144+
{
145+
value_ -= n;
146+
}
147+
return *this;
148+
}
149+
150+
constexpr value_type operator[](difference_type n) const
151+
requires advanceable<value_type>
152+
{
153+
return Start(value_ + n);
154+
}
155+
156+
friend constexpr bool operator==(iota_iterator const & x, iota_iterator const & y)
157+
requires std::equality_comparable<value_type>
158+
{
159+
return x.value_ == y.value_;
160+
}
161+
162+
friend constexpr bool operator<(iota_iterator const & x, iota_iterator const & y)
163+
requires std::totally_ordered<value_type>
164+
{
165+
return x.value_ < y.value_;
166+
}
167+
168+
friend constexpr bool operator>(iota_iterator const & x, iota_iterator const & y)
169+
requires std::totally_ordered<value_type>
170+
{
171+
return y < x;
172+
}
173+
174+
friend constexpr bool operator<=(iota_iterator const & x, iota_iterator const & y)
175+
requires std::totally_ordered<value_type>
176+
{
177+
return !(y < x);
178+
}
179+
180+
friend constexpr bool operator>=(iota_iterator const & x, iota_iterator const & y)
181+
requires std::totally_ordered<value_type>
182+
{
183+
return !(x < y);
184+
}
185+
186+
friend constexpr auto operator<=>(iota_iterator const & x, iota_iterator const & y)
187+
requires std::totally_ordered<value_type> && std::three_way_comparable<value_type>
188+
{
189+
return x.value_ <=> y.value_;
190+
}
191+
192+
friend constexpr iota_iterator operator+(iota_iterator i, difference_type n)
193+
requires advanceable<value_type>
194+
{
195+
i += n;
196+
return i;
197+
}
198+
199+
friend constexpr iota_iterator operator+(difference_type n, iota_iterator i)
200+
requires advanceable<value_type>
201+
{
202+
return i + n;
203+
}
204+
205+
friend constexpr iota_iterator operator-(iota_iterator i, difference_type n)
206+
requires advanceable<value_type>
207+
{
208+
i -= n;
209+
return i;
210+
}
211+
212+
friend constexpr difference_type operator-(iota_iterator const & x, iota_iterator const & y)
213+
requires advanceable<value_type>
214+
{
215+
if constexpr (std::is_integral_v<value_type>)
216+
{
217+
if constexpr (std::is_signed_v<value_type>)
218+
{
219+
return difference_type(difference_type(x.value_) - difference_type(y.value_));
220+
}
221+
222+
if (y.value_ > x.value_)
223+
return difference_type(-difference_type(y.value_ - x.value_));
224+
225+
return difference_type(x.value_ - y.value_);
226+
}
227+
return x.value_ - y.value_;
228+
}
229+
};
230+
231+
template <typename Start, typename BoundSentinel>
232+
class iota_sentinel
233+
{
234+
BoundSentinel bound_{};
235+
236+
public:
237+
constexpr iota_sentinel() = default;
238+
constexpr explicit iota_sentinel(BoundSentinel b) : bound_(std::move(b)) {}
239+
240+
friend constexpr bool operator==(iota_iterator<Start> const & it, iota_sentinel const & sent)
241+
{
242+
return *it == sent.bound_;
243+
}
244+
friend constexpr auto operator-(iota_iterator<Start> const & it, iota_sentinel const & sent)
245+
requires std::sized_sentinel_for<BoundSentinel, Start>
246+
{
247+
return *it - sent.bound_;
248+
}
249+
friend constexpr auto operator-(iota_sentinel const & sent, iota_iterator<Start> const & it)
250+
requires std::sized_sentinel_for<BoundSentinel, Start>
251+
{
252+
return -(it - sent);
253+
}
254+
};
255+
256+
struct iota_fn
257+
{
258+
template <typename Value, typename Bound = std::unreachable_sentinel_t>
259+
constexpr auto operator()(Value val, Bound bound = std::unreachable_sentinel) const
260+
{
261+
static_assert(std::incrementable<Value>, "The Value type to radr::iota needs to satisfy std::incrementable.");
262+
static_assert(weakly_equality_comparable_with<Value, Bound>,
263+
"The Value type to radr::iota needs to be comparable with the Bound type.");
264+
static_assert(std::semiregular<Bound>, "The Bound type to radr::iota needs to satisfy std::regular.");
265+
266+
using It = detail::iota_iterator<Value>;
267+
using Sen = std::conditional_t<std::same_as<Value, Bound>, It, detail::iota_sentinel<Value, Bound>>;
268+
269+
if constexpr ((std::random_access_iterator<It> && std::same_as<It, Sen>) ||
270+
std::sized_sentinel_for<Bound, Value>)
271+
{
272+
return borrowing_rad<It, Sen, It, Sen, borrowing_rad_kind::sized>{It{val}, It{bound}};
273+
}
274+
else
275+
{
276+
return borrowing_rad<It, Sen, It, Sen, borrowing_rad_kind::unsized>{It{val}, Sen{bound}};
277+
}
278+
}
279+
};
280+
281+
} // namespace radr::detail
282+
283+
namespace radr
284+
{
285+
286+
/*!\brief A range factory that generates a sequence of elements by repeatedly incrementing an initial value. Can be either bounded or unbounded (infinite).
287+
* \tparam Value The type of \p value; required to be std::incrementable.
288+
* \tparam Bound The type of \p bound; required to be std::semiregular and detail::weakly_equality_comparable_with Value.
289+
* \param[in] value The initial value.
290+
* \param[in] bound The bound; std::unreachable_sentinel by default.
291+
* \details
292+
*
293+
* In contrast to std::views::iota, this factory always returns a multi-pass range and thus requires
294+
* `std::incrementable<Value>` and not just `std::weakly_incrementable<Value>`.
295+
*
296+
* There is radr::iota_sp which is always a single-pass range and does not have this requirement.
297+
*
298+
*/
299+
inline constexpr detail::iota_fn iota{};
300+
301+
/*!\brief Single-pass version of radr::iota.
302+
* \tparam Value The type of \p value; required to be std::weakly_incrementable.
303+
* \tparam Bound The type of \p bound; required to be detail::weakly_equality_comparable_with Value.
304+
* \param[in] value The initial value.
305+
* \param[in] bound The bound; std::unreachable_sentinel by default.
306+
* \details
307+
*
308+
* This factory always returns a radr::generator, a move-only, single-pass range.
309+
*
310+
* The requirements on the types are weaker than for radr::iota.
311+
*/
312+
inline constexpr auto iota_sp =
313+
detail::overloaded{[]<typename Value, typename Bound>(Value val, Bound bound) -> radr::generator<Value>
314+
{
315+
static_assert(std::weakly_incrementable<Value>,
316+
"The Value type to radr::iota_sp needs to satisfy std::weakly_incrementable.");
317+
static_assert(detail::weakly_equality_comparable_with<Value, Bound>,
318+
"The Value type to radr::iota_sp needs to be comparable with the Bound type.");
319+
320+
while (val != bound)
321+
{
322+
co_yield val;
323+
++val;
324+
}
325+
},
326+
[]<typename Value>(Value val) -> radr::generator<Value>
327+
{
328+
while (true)
329+
{
330+
co_yield val;
331+
++val;
332+
}
333+
}};
334+
335+
} // namespace radr

tests/unit/factory/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
radr_unit_test(empty)
2+
radr_unit_test(iota)
23
radr_unit_test(istream)
34
radr_unit_test(single)
45
radr_unit_test(repeat)

0 commit comments

Comments
 (0)