Skip to content

Commit 4aa9615

Browse files
committed
Introduce DoubleEndedIterator concept
Vec, Array, and Slice iterators all implement DoubleEndedIterator as they know their full size.
1 parent e452392 commit 4aa9615

File tree

8 files changed

+154
-24
lines changed

8 files changed

+154
-24
lines changed

subspace/containers/__private/array_iter.h

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,29 +41,38 @@ struct ArrayIntoIter final
4141
}
4242

4343
Option<Item> next() noexcept final {
44-
if (next_index_.primitive_value == N) [[unlikely]]
44+
if (front_index_ == back_index_) [[unlikely]]
4545
return Option<Item>::none();
46-
// SAFETY: The array has a fixed size. The next_index_ is encapsulated and
47-
// only changed in this class/method. The next_index_ stops incrementing
48-
// when it reaches N and starts at 0, and N >= 0, so when we get here we
49-
// know next_index_ is in range of the array. We use get_unchecked_mut()
50-
// here because it's difficult for the compiler to make the same
51-
// observations we have here, as next_index_ is a field and changes across
52-
// multiple method calls.
46+
// SAFETY: The front and back indicies are kept within the length of the
47+
// Array so can not go out of bounds.
5348
Item& item = array_.get_unchecked_mut(
5449
::sus::marker::unsafe_fn,
55-
::sus::mem::replace(mref(next_index_), next_index_ + 1_usize));
50+
::sus::mem::replace(mref(front_index_), front_index_ + 1_usize));
51+
return Option<Item>::some(move(item));
52+
}
53+
54+
// sus::iter::DoubleEndedIterator trait.
55+
Option<Item> next_back() noexcept {
56+
if (front_index_ == back_index_) [[unlikely]]
57+
return Option<Item>::none();
58+
// SAFETY: The front and back indicies are kept within the length of the
59+
// Array so can not go out of bounds.
60+
back_index_ -= 1u;
61+
Item& item =
62+
array_.get_unchecked_mut(::sus::marker::unsafe_fn, back_index_);
5663
return Option<Item>::some(move(item));
5764
}
5865

5966
private:
6067
ArrayIntoIter(Array<Item, N>&& array) noexcept : array_(::sus::move(array)) {}
6168

62-
usize next_index_ = 0_usize;
6369
Array<Item, N> array_;
70+
usize front_index_ = 0_usize;
71+
usize back_index_ = N;
6472

6573
sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn,
66-
decltype(next_index_),
74+
decltype(front_index_),
75+
decltype(back_index_),
6776
decltype(array_));
6877
};
6978

subspace/containers/__private/slice_iter.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ struct [[sus_trivial_abi]] SliceIter final
5757
return Option<Item>::some(*::sus::mem::replace_ptr(mref(ptr_), ptr_ + 1u));
5858
}
5959

60+
// sus::iter::DoubleEndedIterator trait.
61+
Option<Item> next_back() noexcept {
62+
if (ptr_ == end_) [[unlikely]]
63+
return Option<Item>::none();
64+
// SAFETY: Since end_ > ptr_, which is checked in the constructor, end_ - 1
65+
// will never be null.
66+
end_ -= 1u;
67+
return Option<Item>::some(*end_);
68+
}
69+
6070
::sus::iter::SizeHint size_hint() noexcept final {
6171
// SAFETY: end_ is always larger than ptr_ which is only incremented until
6272
// end_, so this static cast does not drop a negative sign bit. That ptr_
@@ -106,6 +116,16 @@ struct [[sus_trivial_abi]] SliceIterMut final
106116
mref(*::sus::mem::replace_ptr(mref(ptr_), ptr_ + 1u)));
107117
}
108118

119+
// sus::iter::DoubleEndedIterator trait.
120+
Option<Item> next_back() noexcept {
121+
if (ptr_ == end_) [[unlikely]]
122+
return Option<Item>::none();
123+
// SAFETY: Since end_ > ptr_, which is checked in the constructor, end_ - 1
124+
// will never be null.
125+
end_ -= 1u;
126+
return Option<Item>::some(mref(*end_));
127+
}
128+
109129
::sus::iter::SizeHint size_hint() noexcept final {
110130
// SAFETY: end_ is always larger than ptr_ which is only incremented until
111131
// end_, so this static cast does not drop a negative sign bit. That ptr_

subspace/containers/__private/vec_iter.h

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,35 +40,48 @@ struct VecIntoIter final
4040
}
4141

4242
Option<Item> next() noexcept final {
43-
if (next_index_.primitive_value == vec_.len()) [[unlikely]]
43+
if (front_index_ == back_index_) [[unlikely]]
4444
return Option<Item>::none();
45-
// SAFETY: The array has a fixed size. The next_index_ is encapsulated and
46-
// only changed in this class/method. The next_index_ stops incrementing
47-
// when it reaches N and starts at 0, and N >= 0, so when we get here we
48-
// know next_index_ is in range of the array. We use get_unchecked_mut()
49-
// here because it's difficult for the compiler to make the same
50-
// observations we have here, as next_index_ is a field and changes across
51-
// multiple method calls.
45+
// SAFETY: This class owns the Vec and does not expose it, so its length is
46+
// known and can not change. Thus the indices which are kept within the
47+
// length of the Vec can not go out of bounds.
5248
Item& item = vec_.get_unchecked_mut(
5349
::sus::marker::unsafe_fn,
54-
::sus::mem::replace(mref(next_index_), next_index_ + 1_usize));
50+
::sus::mem::replace(mref(front_index_), front_index_ + 1_usize));
51+
return Option<Item>::some(move(item));
52+
}
53+
54+
// sus::iter::DoubleEndedIterator trait.
55+
Option<Item> next_back() noexcept {
56+
if (front_index_ == back_index_) [[unlikely]]
57+
return Option<Item>::none();
58+
// SAFETY: This class owns the Vec and does not expose it, so its length is
59+
// known and can not change. Thus the indices which are kept within the
60+
// length of the Vec can not go out of bounds.
61+
back_index_ -= 1u;
62+
Item& item = vec_.get_unchecked_mut(::sus::marker::unsafe_fn, back_index_);
5563
return Option<Item>::some(move(item));
5664
}
5765

5866
::sus::iter::SizeHint size_hint() noexcept final {
59-
const usize remaining = vec_.len() - next_index_;
67+
const usize remaining = back_index_ - front_index_;
6068
return ::sus::iter::SizeHint(
6169
remaining, ::sus::Option<::sus::num::usize>::some(remaining));
6270
}
6371

6472
private:
6573
VecIntoIter(Vec<Item>&& vec) noexcept : vec_(::sus::move(vec)) {}
6674

67-
usize next_index_ = 0_usize;
75+
// TODO: Decompose the Vec (with a Vec::into_raw_parts) into a (pointer, len,
76+
// cap) and just store the pointer here, as the front_index_ and back_index_
77+
// are all we need to retain wrt the Vec's size.
6878
Vec<Item> vec_;
79+
usize front_index_ = 0_usize;
80+
usize back_index_ = vec_.len();
6981

7082
sus_class_trivially_relocatable_if_types(::sus::marker::unsafe_fn,
71-
decltype(next_index_),
83+
decltype(front_index_),
84+
decltype(back_index_),
7285
decltype(vec_));
7386
};
7487

subspace/containers/array_unittest.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,16 @@ TEST(Array, IntoIter) {
436436
EXPECT_EQ(sum, 15_usize);
437437
}
438438

439+
TEST(Array, IntoIterDoubleEnded) {
440+
auto a = Array<usize, 3>::with_values(1u, 2u, 3u);
441+
442+
auto it = sus::move(a).into_iter();
443+
EXPECT_EQ(it.next_back(), sus::some(3_usize).construct());
444+
EXPECT_EQ(it.next_back(), sus::some(2_usize).construct());
445+
EXPECT_EQ(it.next_back(), sus::some(1_usize).construct());
446+
EXPECT_EQ(it.next_back(), sus::None);
447+
}
448+
439449
TEST(Array, ImplicitIter) {
440450
const auto a = Array<usize, 5>::with_value(3u);
441451
auto sum = 0_usize;

subspace/containers/slice_unittest.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,29 @@ TEST(Slice, IntoIter) {
294294
}
295295
}
296296

297+
TEST(Slice, DoubleEndedIterator) {
298+
{
299+
const usize ar[] = {1u, 2u, 3u};
300+
auto slice = Slice<const usize>::from(ar);
301+
302+
auto it = slice.iter();
303+
EXPECT_EQ(it.next_back(), sus::some(3_usize).construct());
304+
EXPECT_EQ(it.next_back(), sus::some(2_usize).construct());
305+
EXPECT_EQ(it.next_back(), sus::some(1_usize).construct());
306+
EXPECT_EQ(it.next_back(), sus::None);
307+
}
308+
{
309+
usize ar[] = {1u, 2u, 3u};
310+
auto slice = Slice<usize>::from(ar);
311+
312+
auto it = slice.iter_mut();
313+
EXPECT_EQ(it.next_back(), sus::some(3_usize).construct());
314+
EXPECT_EQ(it.next_back(), sus::some(2_usize).construct());
315+
EXPECT_EQ(it.next_back(), sus::some(1_usize).construct());
316+
EXPECT_EQ(it.next_back(), sus::None);
317+
}
318+
}
319+
297320
TEST(Slice, ImplicitIter) {
298321
const usize ar[] = {1u, 2u, 3u};
299322
auto slice = Slice<const usize>::from(ar);

subspace/containers/vec_unittest.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,19 @@ TEST(Vec, IntoIter) {
316316
EXPECT_EQ(sum, 6);
317317
}
318318

319+
TEST(Vec, IntoIterDoubleEnded) {
320+
auto v = Vec<i32>();
321+
v.push(1_i32);
322+
v.push(2_i32);
323+
v.push(3_i32);
324+
325+
auto it = sus::move(v).into_iter();
326+
EXPECT_EQ(it.next_back(), sus::some(3_i32).construct());
327+
EXPECT_EQ(it.next_back(), sus::some(2_i32).construct());
328+
EXPECT_EQ(it.next_back(), sus::some(1_i32).construct());
329+
EXPECT_EQ(it.next_back(), sus::None);
330+
}
331+
319332
TEST(Vec, Growth) {
320333
auto v = Vec<i32>();
321334
v.reserve_exact(2_usize);

subspace/iter/iterator_concept.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414

1515
#pragma once
1616

17+
#include <concepts>
1718
#include <type_traits>
1819

1920
#include "subspace/convert/subclass.h"
2021
#include "subspace/mem/forward.h"
22+
#include "subspace/option/option.h"
2123

2224
namespace sus::iter {
2325

@@ -26,9 +28,22 @@ class IteratorImpl;
2628

2729
/// A concept for all implementations of iterators.
2830
///
31+
/// An iterator has a method, `next()`, which when called, returns an
32+
/// `Option<Item>`. Calling next will return an `Option` containing the next
33+
/// `Item` as long as there are elements, and once they've all been exhausted,
34+
/// will return `None` to indicate that iteration is finished. Individual
35+
/// iterators may choose to resume iteration, and so calling next again may or
36+
/// may not eventually start returning an `Item` again at some point.
37+
///
2938
/// Types that satisfy this concept can be used in for loops and provide
3039
/// all the methods of an iterator type, which are found in
3140
/// `sus::iter::IteratorImpl`.
41+
///
42+
/// Any Iterator's full definition includes a number of other methods as well,
43+
/// built on top of next, and so you get them for free.
44+
///
45+
/// Iterators are also composable, and it's possible to chain them together to
46+
/// do more complex forms of processing.
3247
template <class T, class Item>
3348
concept Iterator = requires {
3449
// Has a T::Item typename.
@@ -40,6 +55,24 @@ concept Iterator = requires {
4055
std::decay_t<T>*, IteratorImpl<std::decay_t<T>, Item>*>;
4156
};
4257

58+
/// An `Iterator` able to yield elements from both ends.
59+
///
60+
/// Something that implements `DoubleEndedIterator` has one extra capability
61+
/// over something that implements `Iterator`: the ability to also take Items
62+
/// from the back, as well as the front.
63+
///
64+
/// It is important to note that both back and forth work on the same range, and
65+
/// do not cross: iteration is over when they meet in the middle.
66+
///
67+
/// In a similar fashion to the `Iterator` protocol, once a
68+
/// `DoubleEndedIterator` returns `None` from a `next_back()`, calling it again
69+
/// may or may not ever return `Some` again. `next()` and `next_back()` are
70+
/// interchangeable for this purpose.
71+
template <class T, class Item>
72+
concept DoubleEndedIterator = Iterator<T, Item> && requires(T& t) {
73+
{ t.next_back() } -> std::same_as<::sus::option::Option<Item>>;
74+
};
75+
4376
/// Conversion into an `Iterator`.
4477
///
4578
/// A more general trait than `Iterator` which will accept anything that can be

subspace/iter/iterator_unittest.cc

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414

1515
#include "subspace/iter/iterator.h"
1616

17-
#include "subspace/iter/empty.h"
1817
#include "googletest/include/gtest/gtest.h"
1918
#include "subspace/assertions/unreachable.h"
2019
#include "subspace/construct/into.h"
2120
#include "subspace/containers/array.h"
2221
#include "subspace/containers/vec.h"
22+
#include "subspace/iter/empty.h"
2323
#include "subspace/iter/filter.h"
2424
#include "subspace/macros/__private/compiler_bugs.h"
2525
#include "subspace/prelude.h"
@@ -52,6 +52,15 @@ static_assert(
5252
static_assert(
5353
sus::iter::Iterator<sus::containers::SliceIterMut<int&>, int&>);
5454

55+
static_assert(
56+
sus::iter::DoubleEndedIterator<sus::containers::ArrayIntoIter<int, 1>, int>);
57+
static_assert(
58+
sus::iter::DoubleEndedIterator<sus::containers::SliceIter<const int&>, const int&>);
59+
static_assert(
60+
sus::iter::DoubleEndedIterator<sus::containers::SliceIterMut<int&>, int&>);
61+
static_assert(
62+
sus::iter::DoubleEndedIterator<sus::containers::VecIntoIter<int>, int>);
63+
5564
static_assert(
5665
sus::iter::IntoIterator<sus::containers::Array<int, 3u>, int>);
5766
static_assert(

0 commit comments

Comments
 (0)