@@ -62,13 +62,81 @@ class LockFreeSpscQueue
6262 * @brief An RAII scope object representing a prepared write operation.
6363 * @details Provides direct `std::span` access to the writable blocks in the
6464 * underlying buffer. The transaction is committed when this object
65- * is destroyed. It is move-only.
65+ * is destroyed. This object also satisfies the `std::ranges::range`
66+ * concept, allowing for direct iteration over the writable slots.
67+ * It is move-only.
6668 * @warning The user MUST write a number of items exactly equal to the value
6769 * returned by `get_items_written()`. Failure to do so will result
6870 * in the consumer reading uninitialized/garbage data.
6971 */
7072 struct WriteScope
7173 {
74+ // --- Custom Iterator for WriteScope ---
75+ class iterator
76+ {
77+ public:
78+ using iterator_category = std::forward_iterator_tag;
79+ using value_type = T;
80+ using difference_type = std::ptrdiff_t ;
81+ using pointer = T*;
82+ using reference = T&;
83+ using SpanIterator = typename std::span<T>::iterator;
84+
85+ iterator () = default ;
86+ reference operator *() const { return *m_current_iter; }
87+ pointer operator ->() const { return &(*m_current_iter); }
88+
89+ iterator& operator ++() {
90+ ++m_current_iter;
91+ if (m_in_block1 && m_current_iter == m_block1_end) {
92+ m_current_iter = m_block2_begin;
93+ m_in_block1 = false ;
94+ }
95+ return *this ;
96+ }
97+ iterator operator ++(int ) { iterator tmp = *this ; ++(*this ); return tmp; }
98+ bool operator ==(const iterator& other) const = default ;
99+
100+ private:
101+ friend struct WriteScope ;
102+ iterator (SpanIterator b1_begin, SpanIterator b1_end,
103+ SpanIterator b2_begin, SpanIterator b2_end,
104+ bool is_begin)
105+ : m_block1_end(b1_end), m_block2_begin(b2_begin), m_block2_end(b2_end)
106+ {
107+ if (is_begin) {
108+ if (b1_begin != b1_end) { // If block1 is not empty, start there.
109+ m_current_iter = b1_begin;
110+ m_in_block1 = true ;
111+ } else { // Otherwise, start at block2.
112+ m_current_iter = b2_begin;
113+ m_in_block1 = false ;
114+ }
115+ } else { // This is the end() sentinel iterator.
116+ m_current_iter = m_block2_end; // The end is always the end of block2.
117+ m_in_block1 = false ;
118+ }
119+ }
120+
121+ SpanIterator m_current_iter;
122+ SpanIterator m_block1_end;
123+ SpanIterator m_block2_begin;
124+ SpanIterator m_block2_end;
125+ bool m_in_block1 = false ;
126+ };
127+
128+ // --- Making WriteScope a C++20 Range ---
129+ [[nodiscard]] iterator begin () {
130+ auto b1 = get_block1 ();
131+ auto b2 = get_block2 ();
132+ return iterator (b1.begin (), b1.end (), b2.begin (), b2.end (), true );
133+ }
134+ [[nodiscard]] iterator end () {
135+ auto b1 = get_block1 ();
136+ auto b2 = get_block2 ();
137+ return iterator (b1.begin (), b1.end (), b2.begin (), b2.end (), false );
138+ }
139+
72140 /* * @brief Returns a span representing the first contiguous block to write to. */
73141 [[nodiscard]] std::span<T> get_block1 () const
74142 {
@@ -103,9 +171,9 @@ class LockFreeSpscQueue
103171 WriteScope& operator =(const WriteScope&) = delete ;
104172
105173 WriteScope (WriteScope&& other) noexcept
106- : start_index1(other.start_index1), block_size1(other.block_size1),
107- start_index2 (other.start_index2), block_size2(other.block_size2),
108- m_owner_queue(other.m_owner_queue)
174+ : start_index1(other.start_index1), block_size1(other.block_size1)
175+ , start_index2(other.start_index2), block_size2(other.block_size2)
176+ , m_owner_queue(other.m_owner_queue)
109177 { other.m_owner_queue = nullptr ; }
110178
111179 // Move assignment is deleted. It is not possible to assign to the const members,
@@ -131,13 +199,80 @@ class LockFreeSpscQueue
131199 * @brief An RAII scope object representing a prepared read operation.
132200 * @details Provides direct `std::span` access to the readable blocks in the
133201 * underlying buffer. The transaction is committed when this object
134- * is destroyed. It is move-only.
202+ * is destroyed. This object also satisfies the `std::ranges::range`
203+ * concept, allowing direct iteration. It is move-only.
135204 * @warning The user MUST treat all data within the returned spans as read.
136205 * The full `get_items_read()` amount will be committed, advancing
137206 * the read pointer and making the space available for future writes.
138207 */
139208 struct ReadScope
140209 {
210+ // --- Custom Iterator for ReadScope ---
211+ class iterator
212+ {
213+ public:
214+ using iterator_category = std::forward_iterator_tag;
215+ using value_type = const T;
216+ using difference_type = std::ptrdiff_t ;
217+ using pointer = const T*;
218+ using reference = const T&;
219+ using SpanConstIterator = typename std::span<const T>::iterator;
220+
221+ iterator () = default ;
222+ reference operator *() const { return *m_current_iter; }
223+ pointer operator ->() const { return &(*m_current_iter); }
224+
225+ iterator& operator ++() {
226+ ++m_current_iter;
227+ if (m_in_block1 && m_current_iter == m_block1_end) {
228+ m_current_iter = m_block2_begin;
229+ m_in_block1 = false ;
230+ }
231+ return *this ;
232+ }
233+ iterator operator ++(int ) { iterator tmp = *this ; ++(*this ); return tmp; }
234+ bool operator ==(const iterator& other) const = default ;
235+
236+ private:
237+ friend struct ReadScope ;
238+ iterator (SpanConstIterator b1_begin, SpanConstIterator b1_end,
239+ SpanConstIterator b2_begin, SpanConstIterator b2_end,
240+ bool is_begin)
241+ : m_block1_end(b1_end), m_block2_begin(b2_begin), m_block2_end(b2_end)
242+ {
243+ if (is_begin) {
244+ if (b1_begin != b1_end) {
245+ m_current_iter = b1_begin;
246+ m_in_block1 = true ;
247+ } else {
248+ m_current_iter = b2_begin;
249+ m_in_block1 = false ;
250+ }
251+ } else {
252+ m_current_iter = m_block2_end;
253+ m_in_block1 = false ;
254+ }
255+ }
256+
257+ SpanConstIterator m_current_iter;
258+ SpanConstIterator m_block1_end;
259+ SpanConstIterator m_block2_begin;
260+ SpanConstIterator m_block2_end;
261+ bool m_in_block1 = false ;
262+ };
263+
264+ // --- Making ReadScope a C++20 Range ---
265+ [[nodiscard]] iterator begin () const {
266+ auto b1 = get_block1 ();
267+ auto b2 = get_block2 ();
268+ return iterator (b1.begin (), b1.end (), b2.begin (), b2.end (), true );
269+ }
270+ [[nodiscard]] iterator end () const {
271+ auto b1 = get_block1 ();
272+ auto b2 = get_block2 ();
273+ return iterator (b1.begin (), b1.end (), b2.begin (), b2.end (), false );
274+ }
275+
141276 /* * @brief Returns a span representing the first contiguous block to read from. */
142277 [[nodiscard]] std::span<const T> get_block1 () const
143278 {
0 commit comments