Skip to content

Commit 6b0c73d

Browse files
committed
Add start_at() and end_at() to RangeBounds to add/adjust bounds
1 parent 27a5dd1 commit 6b0c73d

File tree

3 files changed

+142
-21
lines changed

3 files changed

+142
-21
lines changed

subspace/ops/range.h

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,23 @@ namespace sus::ops {
2525
/// range syntax like `..`, `a..`, `..b`, `..=c`, `d..e`, or `f..=g`.
2626
template <class T, class I>
2727
concept RangeBounds = requires(const T& t, T v, I i) {
28-
{ t.start_bound() } -> std::same_as<::sus::option::Option<const I&>>;
28+
{ t.start_bound() } -> std::same_as<::sus::option::Option<const I&>>;
2929
{ t.end_bound() } -> std::same_as<::sus::option::Option<const I&>>;
30-
// Rvalue overloads must not exist as they would return a reference to a temporary.
30+
// Rvalue overloads must not exist as they would return a reference to a
31+
// temporary.
3132
requires !requires { ::sus::move(v).start_bound(); };
3233
requires !requires { ::sus::move(v).end_bound(); };
3334
{ t.contains(i) } -> std::same_as<bool>;
35+
// These should return a RangeBounds, but we're unable to check that here as
36+
// the type may return itself and the concept would be cyclical and thus
37+
// become false.
38+
{ ::sus::move(v).start_at(i) };
39+
{ ::sus::move(v).end_at(i) };
40+
// start_at() and end_at() are rvalue methods.
41+
requires !requires { t.start_at(i); };
42+
requires !requires { t.end_at(i); };
43+
requires !requires { v.start_at(i); };
44+
requires !requires { v.end_at(i); };
3445
};
3546

3647
/// A (half-open) range bounded inclusively below and exclusively above
@@ -55,9 +66,8 @@ class Range final : public ::sus::iter::IteratorImpl<Range<T>, T> {
5566
constexpr Range() noexcept
5667
requires(::sus::construct::Default<T>)
5768
= default;
58-
static constexpr Range with(T start, T finish) noexcept {
59-
return Range(::sus::move(start), ::sus::move(finish));
60-
}
69+
constexpr Range(T start, T finish) noexcept
70+
: start(::sus::move(start)), finish(::sus::move(finish)) {}
6171

6272
/// Returns true if `item` is contained in the range.
6373
//
@@ -85,6 +95,17 @@ class Range final : public ::sus::iter::IteratorImpl<Range<T>, T> {
8595
/// The range is empty if either side is incomparable, such as `f32::NAN`.
8696
constexpr bool is_empty() const noexcept { return !(start < finish); }
8797

98+
/// Return a new Range that starts at `t` and ends where the original Range
99+
/// did.
100+
constexpr Range start_at(T t) && noexcept {
101+
return Range(::sus::move(t), ::sus::move(finish));
102+
}
103+
/// Return a new Range that starts at where the original Range did and ends at
104+
/// `t`.
105+
constexpr Range end_at(T t) && noexcept {
106+
return Range(::sus::move(start), ::sus::move(t));
107+
}
108+
88109
// sus::iter::Iterator trait.
89110
Option<T> next() noexcept final {
90111
if (start == finish) return Option<T>::none();
@@ -108,9 +129,12 @@ class Range final : public ::sus::iter::IteratorImpl<Range<T>, T> {
108129
// TODO: Provide and test overrides of Iterator min(), max(), count(),
109130
// advance_by(), etc that can be done efficiently here.
110131

111-
private:
112-
constexpr Range(T start, T finish) noexcept
113-
: start(::sus::move(start)), finish(::sus::move(finish)) {}
132+
// sus::ops::Eq trait
133+
constexpr bool operator==(const Range& rhs) const noexcept
134+
requires Eq<T>
135+
{
136+
return start == rhs.start && finish == rhs.finish;
137+
}
114138
};
115139

116140
/// A range only bounded inclusively below (`start..`).
@@ -137,9 +161,7 @@ class RangeFrom final : public ::sus::iter::IteratorImpl<RangeFrom<T>, T> {
137161
constexpr RangeFrom() noexcept
138162
requires(::sus::construct::Default<T>)
139163
= default;
140-
static constexpr RangeFrom with(T start) noexcept {
141-
return RangeFrom(::sus::move(start));
142-
}
164+
constexpr RangeFrom(T start) noexcept : start(::sus::move(start)) {}
143165

144166
/// Returns true if `item` is contained in the range.
145167
///
@@ -162,6 +184,16 @@ class RangeFrom final : public ::sus::iter::IteratorImpl<RangeFrom<T>, T> {
162184
}
163185
constexpr ::sus::option::Option<const T&> end_bound() && = delete;
164186

187+
/// Return a new RangeFrom that starts at `t` and still has no end.
188+
constexpr RangeFrom start_at(T t) && noexcept {
189+
return RangeFrom(::sus::move(t));
190+
}
191+
/// Return a new Range that starts at where the original Range did and ends at
192+
/// `t`.
193+
constexpr Range<T> end_at(T t) && noexcept {
194+
return Range<T>(::sus::move(start), ::sus::move(t));
195+
}
196+
165197
// sus::iter::Iterator trait.
166198
Option<T> next() noexcept final {
167199
return Option<T>::some(::sus::mem::replace(start, start + 1u));
@@ -170,8 +202,12 @@ class RangeFrom final : public ::sus::iter::IteratorImpl<RangeFrom<T>, T> {
170202
// TODO: Provide and test overrides of Iterator min(), advance_by(), etc
171203
// that can be done efficiently here.
172204

173-
private:
174-
constexpr RangeFrom(T start) noexcept : start(::sus::move(start)) {}
205+
// sus::ops::Eq trait
206+
constexpr bool operator==(const RangeFrom& rhs) const noexcept
207+
requires Eq<T>
208+
{
209+
return start == rhs.start;
210+
}
175211
};
176212

177213
/// A range only bounded exclusively above (`..end`).
@@ -190,9 +226,7 @@ class RangeTo final {
190226
constexpr RangeTo() noexcept
191227
requires(::sus::construct::Default<T>)
192228
= default;
193-
static constexpr RangeTo with(T finish) noexcept {
194-
return RangeTo(::sus::move(finish));
195-
}
229+
constexpr RangeTo(T finish) noexcept : finish(::sus::move(finish)) {}
196230

197231
/// Returns true if `item` is contained in the range.
198232
///
@@ -215,8 +249,20 @@ class RangeTo final {
215249
}
216250
constexpr ::sus::option::Option<const T&> end_bound() && = delete;
217251

218-
private:
219-
constexpr RangeTo(T finish) noexcept : finish(::sus::move(finish)) {}
252+
/// Return a new Range that starts at `t` and ends where the original Range
253+
/// did.
254+
constexpr Range<T> start_at(T t) && noexcept {
255+
return Range<T>(::sus::move(t), ::sus::move(finish));
256+
}
257+
/// Return a new Range that still has no start and ends at `t`.
258+
constexpr RangeTo end_at(T t) && noexcept { return RangeTo(::sus::move(t)); }
259+
260+
// sus::ops::Eq trait
261+
constexpr bool operator==(const RangeTo& rhs) const noexcept
262+
requires Eq<T>
263+
{
264+
return finish == rhs.finish;
265+
}
220266
};
221267

222268
/// An unbounded range (`..`).
@@ -250,6 +296,22 @@ class RangeFull final {
250296
return ::sus::option::Option<const T&>::none();
251297
}
252298
constexpr ::sus::option::Option<const T&> end_bound() && = delete;
299+
300+
/// Return a new Range that starts at `t` and has no end.
301+
constexpr RangeFrom<T> start_at(T t) && noexcept {
302+
return RangeFrom<T>(::sus::move(t));
303+
}
304+
/// Return a new Range that has no start and ends at `t`.
305+
constexpr RangeTo<T> end_at(T t) && noexcept {
306+
return RangeTo<T>(::sus::move(t));
307+
}
308+
309+
// sus::ops::Eq trait
310+
constexpr bool operator==(const RangeFull& rhs) const noexcept
311+
requires Eq<T>
312+
{
313+
return true;
314+
}
253315
};
254316

255317
} // namespace sus::ops

subspace/ops/range_literals.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,11 @@ constexpr auto operator""_r() {
126126
if constexpr (D.type == RangeLiteralDeducer::NoBound)
127127
return ::sus::ops::RangeFull<::sus::num::usize>();
128128
else if constexpr (D.type == RangeLiteralDeducer::LowerBound)
129-
return ::sus::ops::RangeFrom<::sus::num::usize>::with(D.lower);
129+
return ::sus::ops::RangeFrom<::sus::num::usize>(D.lower);
130130
else if constexpr (D.type == RangeLiteralDeducer::UpperBound)
131-
return ::sus::ops::RangeTo<::sus::num::usize>::with(D.upper);
131+
return ::sus::ops::RangeTo<::sus::num::usize>(D.upper);
132132
else
133-
return ::sus::ops::Range<::sus::num::usize>::with(D.lower, D.upper);
133+
return ::sus::ops::Range<::sus::num::usize>(D.lower, D.upper);
134134
}
135135

136136
// TODO: _rs to make a signed RangeBounds over `isize`?

subspace/ops/range_unittest.cc

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919

2020
namespace {
2121

22+
using sus::ops::Range;
23+
using sus::ops::RangeFrom;
24+
using sus::ops::RangeFull;
25+
using sus::ops::RangeTo;
26+
2227
// Range* satisifies RangeBounds.
2328
static_assert(sus::ops::RangeBounds<sus::ops::Range<usize>, usize>);
2429
static_assert(sus::ops::RangeBounds<sus::ops::RangeFrom<usize>, usize>);
@@ -92,6 +97,60 @@ static_assert([]() constexpr {
9297
// auto x13 = "1..='2"_r;
9398
// auto x14 = "1..=2'"_r;
9499

100+
// start_at() and end_at().
101+
static_assert([]() constexpr {
102+
auto r = "1..5"_r.start_at(8u);
103+
return r == Range(8_usize, 5_usize);
104+
}());
105+
static_assert([]() constexpr {
106+
auto r = "1..5"_r.start_at(8u);
107+
return r == Range(8_usize, 5_usize);
108+
}());
109+
static_assert([]() constexpr {
110+
auto r = "1..5"_r.end_at(8u);
111+
return r == Range(1_usize, 8_usize);
112+
}());
113+
static_assert([]() constexpr {
114+
auto r = "1..5"_r.start_at(8u).end_at(9u);
115+
return r == Range(8_usize, 9_usize);
116+
}());
117+
static_assert([]() constexpr {
118+
auto r = "1.."_r.start_at(8u);
119+
return r == RangeFrom(8_usize);
120+
}());
121+
static_assert([]() constexpr {
122+
auto r = "1.."_r.end_at(8u);
123+
return r == Range(1_usize, 8_usize);
124+
}());
125+
static_assert([]() constexpr {
126+
auto r = "1..5"_r.start_at(8u).end_at(9u);
127+
return r == Range(8_usize, 9_usize);
128+
}());
129+
static_assert([]() constexpr {
130+
auto r = "..5"_r.start_at(8u);
131+
return r == Range(8_usize, 5_usize);
132+
}());
133+
static_assert([]() constexpr {
134+
auto r = "..5"_r.end_at(8u);
135+
return r == RangeTo(8_usize);
136+
}());
137+
static_assert([]() constexpr {
138+
auto r = "..5"_r.start_at(8u).end_at(9u);
139+
return r == Range(8_usize, 9_usize);
140+
}());
141+
static_assert([]() constexpr {
142+
auto r = ".."_r.start_at(8u);
143+
return r == RangeFrom(8_usize);
144+
}());
145+
static_assert([]() constexpr {
146+
auto r = ".."_r.end_at(8u);
147+
return r == RangeTo(8_usize);
148+
}());
149+
static_assert([]() constexpr {
150+
auto r = ".."_r.start_at(8u).end_at(9u);
151+
return r == Range(8_usize, 9_usize);
152+
}());
153+
95154
TEST(Range, Iter) {
96155
auto it = "1..5"_r;
97156
EXPECT_EQ(it.next().unwrap(), 1u);

0 commit comments

Comments
 (0)