Stroika Library 3.0d24
 
Loading...
Searching...
No Matches
LinkedList.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include <optional>
5
7
9
10// Would like to leave on by default but we just added and cannot afford to have debug builds get that slow
11#ifndef qStroika_Foundation_Containers_DataStructures_LinkedList_IncludeSlowDebugChecks_
12#define qStroika_Foundation_Containers_DataStructures_LinkedList_IncludeSlowDebugChecks_ 0
13#endif
14
15 /*
16 ********************************************************************************
17 ************************ LinkedList<T,TRAITS>::Link_ ***************************
18 ********************************************************************************
19 */
20 template <typename T>
21 constexpr LinkedList<T>::Link_::Link_ (ArgByValueType<T> item, Link_* next)
22 : fItem{item}
23 , fNext{next}
24 {
25 }
26
27 /*
28 ********************************************************************************
29 **************************** LinkedList<T,TRAITS> ******************************
30 ********************************************************************************
31 */
32 template <typename T>
33 inline LinkedList<T>::LinkedList ()
34 {
35 Invariant ();
36 }
37 template <typename T>
38 inline LinkedList<T>::LinkedList (LinkedList&& src) noexcept
39 : fHead_{src.fHead_}
40 , fLength_{src.fLength_}
41 {
42 src.fHead_ = nullptr;
43 src.fLength_ = 0;
44 Invariant ();
45 src.Invariant ();
46 }
47 template <typename T>
48 LinkedList<T>::LinkedList (const LinkedList& src)
49 {
50 /*
51 * Copy the link list by keeping a pointer to the new current and new
52 * previous, and sliding them along in parallel as we construct the
53 * new list. Only do this if we have at least one element - then we
54 * don't have to worry about the head of the list, or nullptr ptrs, etc - that
55 * case is handled outside, before the loop.
56 */
57 // NB: fLength_ is bumped as each link is attached, NOT assigned from src at the end. A
58 // 'new Link_' can throw - the allocation, or T's copy CTOR, since Link_ holds a T by value -
59 // and counting incrementally keeps fLength_ equal to the links actually present if it does.
60 //
61 // The try/catch is REQUIRED, and is not the same case as operator=: a CTOR that throws leaves an
62 // object that never finished constructing, so ~LinkedList () is never run for it, so nothing else
63 // will ever free the links built so far. Each new Link_ is created with a null fNext and only
64 // linked in afterwards, so the chain is always well formed at the throw point and clear () can
65 // walk it.
66 try {
67 if (src.fHead_ != nullptr) {
68 fHead_ = new Link_{src.fHead_->fItem, nullptr};
69 ++fLength_;
70 Link_* newCur = fHead_;
71 for (const Link_* cur = src.fHead_->fNext; cur != nullptr; cur = cur->fNext) {
72 Link_* newPrev = newCur;
73 newCur = new Link_{cur->fItem, nullptr};
74 newPrev->fNext = newCur;
75 ++fLength_;
76 }
77 }
78 }
79 catch (...) {
80 clear ();
81 throw;
82 }
83 Invariant ();
84 }
85 template <typename T>
86 inline LinkedList<T>::~LinkedList ()
87 {
88 /*
89 * This could be a little cheaper - we could avoid setting fLength field,
90 * and fHead_ pointer, but we must worry more about codeSize/re-use.
91 * That would involve a new function that COULD NOT BE INLINED.
92 *
93 * < I guess I could add a hack method - unadvertised - but has to be
94 * at least protected - and call it here to do what I've mentioned above >
95 */
96 Invariant ();
97 clear ();
98 Invariant ();
99 Ensure (fHead_ == nullptr);
100 }
101 template <typename T>
103 {
105 Invariant ();
106 if (this != &rhs) {
107 clear ();
108 /*
109 * Copy the link list by keeping a point to the new current and new
110 * previous, and sliding them along in parallel as we construct the
111 * new list. Only do this if we have at least one element - then we
112 * don't have to worry about the head of the list, or nullptr ptrs, etc - that
113 * case is handled outside, before the loop.
114 */
115 // count as we link (see the note in the copy CTOR): if a 'new Link_' throws partway, this
116 // object SURVIVES the failed assignment, so fLength_ must still match the links built so far
117 if (rhs.fHead_ != nullptr) {
118 fHead_ = new Link_{rhs.fHead_->fItem, nullptr};
119 ++fLength_;
120 Link_* newCur = fHead_;
121 for (const Link_* cur = rhs.fHead_->fNext; cur != nullptr; cur = cur->fNext) {
122 Link_* newPrev = newCur;
123 newCur = new Link_{cur->fItem, nullptr};
124 newPrev->fNext = newCur;
125 ++fLength_;
127 }
128 }
129 Invariant ();
130 return *this;
131 }
132 template <typename T>
133 inline void LinkedList<T>::Invariant () const noexcept
134 {
135#if qStroika_Foundation_Debug_AssertionsChecked
136 Invariant_ ();
137#endif
138 }
139 template <typename T>
140 inline void LinkedList<T>::MoveIteratorHereAfterClone (ForwardIterator* pi, const LinkedList* movedFrom) const
141 {
142 AssertExternallySynchronizedChecker::ReadContext declareContext{*this};
143 // TRICKY TODO - BUT MUST DO - MUST MOVE FROM OLD ITER TO NEW
144 // only way
145 //
146 // For STL containers, not sure how to find an equiv new iterator for an old one, but my best guess is to iterate through
147 // old for old, and when I match, stop on new
148#if qStroika_Foundation_Debug_AssertionsChecked
149 Require (pi->fData_ == movedFrom);
150#endif
151 auto newI = this->fHead_;
152 [[maybe_unused]] auto newE = nullptr;
153 auto oldI = movedFrom->fHead_;
154 [[maybe_unused]] auto oldE = nullptr;
155 while (oldI != pi->fCurrent_) {
156 Assert (newI != newE);
157 Assert (oldI != oldE);
158 newI = newI->fNext;
159 oldI = oldI->fNext;
160 Assert (newI != newE);
161 Assert (oldI != oldE);
162 }
163 Assert (oldI == pi->fCurrent_);
164 pi->fCurrent_ = newI;
165#if qStroika_Foundation_Debug_AssertionsChecked
166 pi->fData_ = this;
167#endif
168 }
169 template <typename T>
170 inline auto LinkedList<T>::begin () const -> ForwardIterator
171 {
172 AssertExternallySynchronizedChecker::ReadContext declareContext{*this};
173 return ForwardIterator{this};
174 }
175 template <typename T>
176 constexpr auto LinkedList<T>::end () const noexcept -> ForwardIterator
177 {
178 return ForwardIterator{};
179 }
180 template <typename T>
181 inline bool LinkedList<T>::empty () const
182 {
183 AssertExternallySynchronizedChecker::ReadContext declareContext{*this};
184 return fHead_ == nullptr;
185 }
186 template <typename T>
187 inline size_t LinkedList<T>::size () const
188 {
189 AssertExternallySynchronizedChecker::ReadContext declareContext{*this};
190 return fLength_;
191 }
192 template <typename T>
193 inline optional<T> LinkedList<T>::GetFirst () const
194 {
195 AssertExternallySynchronizedChecker::ReadContext declareContext{*this};
196 return fHead_ == nullptr ? optional<T>{} : fHead_->fItem;
198 template <typename T>
199 inline void LinkedList<T>::push_front (ArgByValueType<T> item)
200 {
202 Invariant ();
203 fHead_ = new Link_{item, fHead_};
204 ++fLength_;
205 Invariant ();
206 }
207 template <typename T>
208 template <Memory::ISpanOfT<T> SPAN_T>
209 void LinkedList<T>::push_front (const SPAN_T& copyFrom)
210 {
211 // push_front in reverse order cuz push_front reverses traversal order, and two wrongs make a right
212 for (auto ri = copyFrom.rbegin (); ri != copyFrom.rend (); ++ri) {
213 push_front (*ri);
214 }
215 }
216 template <typename T>
217 void LinkedList<T>::push_back (ArgByValueType<T> item)
218 {
220 if (this->fHead_ == nullptr) [[unlikely]] {
221 push_front (item);
222 }
223 else {
224 Link_* last = this->fHead_;
225 for (; last->fNext != nullptr; last = last->fNext)
226 ;
227 Assert (last != nullptr);
228 Assert (last->fNext == nullptr);
229 last->fNext = new Link_{item, nullptr};
230 ++fLength_;
231 }
232 }
233 template <typename T>
234 template <Memory::ISpanOfT<T> SPAN_T>
235 void LinkedList<T>::push_back (const SPAN_T& copyFrom)
236 {
238 Link_* last = this->fHead_; // Compute last once, and re-use for each item appended
239 if (last != nullptr) {
240 for (; last->fNext != nullptr; last = last->fNext)
241 ;
242 }
243 for (const auto& i : copyFrom) {
244 if (last == nullptr) [[unlikely]] {
245 // list was empty, so the element we just prepended is also the LAST one - track it, or
246 // every subsequent element takes this branch too and the span comes out REVERSED
247 push_front (i);
248 last = fHead_;
249 }
250 else {
251 Assert (last->fNext == nullptr); // really we are last
252 last->fNext = new Link_{i, nullptr};
253 ++fLength_;
254 last = last->fNext; // for next item in span
255 }
256 }
257 }
258 template <typename T>
262 Require (not empty ());
263 AssertNotNull (fHead_);
264 Invariant ();
265 Link_* victim = fHead_;
266 fHead_ = victim->fNext;
267 delete victim;
268 --fLength_;
269 Invariant ();
270 }
271 template <typename T>
272 inline T* LinkedList<T>::PeekAt (const ForwardIterator& i)
273 {
275#if qStroika_Foundation_Debug_AssertionsChecked
276 Require (i.fData_ == this); // assure iterator not stale
277#endif
278 Require (not i.AtEnd ());
279 Invariant ();
280 i.Invariant ();
281 return &const_cast<Link_*> (i.fCurrent_)->fItem;
282 }
283 template <typename T>
284 inline void LinkedList<T>::SetAt (const ForwardIterator& i, ArgByValueType<T> newValue)
285 {
287 Require (not i.AtEnd ());
288#if qStroika_Foundation_Debug_AssertionsChecked
289 Require (i.fData_ == this); // assure iterator not stale
290#endif
291 Invariant ();
292 i.Invariant ();
293 const_cast<Link_*> (i.fCurrent_)->fItem = newValue;
294 Invariant ();
295 }
296 template <typename T>
297 void LinkedList<T>::AddBefore (const ForwardIterator& i, ArgByValueType<T> item)
298 {
300#if qStroika_Foundation_Debug_AssertionsChecked
301 Require (i.fData_ == this); // assure iterator not stale
302#endif
303 /*
304 * NB: This code works fine, even if 'i' is AtEnd ()
305 */
306 Invariant ();
307 i.Invariant ();
308
309 Link_* prev = nullptr;
310 if ((this->fHead_ != nullptr) and (this->fHead_ != i.fCurrent_)) {
311 for (prev = this->fHead_; prev->fNext != i.fCurrent_; prev = prev->fNext) {
312 AssertNotNull (prev); // cuz that would mean fCurrent_ not in LinkedList!!!
313 }
314 }
315
316 if (prev == nullptr) {
317 Assert (this->fHead_ == i.fCurrent_); // could be nullptr, or not...
318 this->fHead_ = new Link_{item, this->fHead_};
319 }
320 else {
321 Assert (prev->fNext == i.fCurrent_);
322 prev->fNext = new Link_{item, prev->fNext};
323 }
324 ++fLength_;
325
326 Invariant ();
327 }
328 template <typename T>
329 void LinkedList<T>::AddBefore (const ForwardIterator& i, ArgByValueType<T> item, ForwardIterator* newLinkCreatedAt)
330 {
331 RequireNotNull (newLinkCreatedAt);
333#if qStroika_Foundation_Debug_AssertionsChecked
334 Require (i.fData_ == this); // assure iterator not stale
335#endif
336 /*
337 * NB: This code works fine, even if 'i' is AtEnd ()
338 */
339 Invariant ();
340 i.Invariant ();
341
342 Link_* prev = nullptr;
343 if ((this->fHead_ != nullptr) and (this->fHead_ != i.fCurrent_)) {
344 for (prev = this->fHead_; prev->fNext != i.fCurrent_; prev = prev->fNext) {
345 AssertNotNull (prev); // cuz that would mean fCurrent_ not in LinkedList!!!
346 }
347 }
348
349 if (prev == nullptr) {
350 Assert (this->fHead_ == i.fCurrent_); // could be nullptr, or not...
351 this->fHead_ = new Link_{item, this->fHead_};
352 *newLinkCreatedAt = ForwardIterator{this, this->fHead_};
353 }
354 else {
355 Assert (prev->fNext == i.fCurrent_);
356 prev->fNext = new Link_{item, prev->fNext};
357 *newLinkCreatedAt = ForwardIterator{this, prev->fNext};
358 }
359 ++fLength_;
360
361 Invariant ();
362 }
363 template <typename T>
364 inline void LinkedList<T>::AddAfter (const ForwardIterator& i, ArgByValueType<T> newValue)
365 {
367 Require (not i.AtEnd ());
368#if qStroika_Foundation_Debug_AssertionsChecked
369 Require (i.fData_ == this); // assure iterator not stale
370#endif
371 AssertNotNull (i.fCurrent_); // since not AtEnd...
372 i.Invariant ();
373 const_cast<Link_*> (i.fCurrent_)->fNext = new Link_{newValue, i.fCurrent_->fNext};
374 ++fLength_;
375 }
376 template <typename T>
377 inline auto LinkedList<T>::erase (const ForwardIterator& i) -> ForwardIterator
378 {
379 ForwardIterator next = i;
380 ++next;
381 Remove (i);
382 next.Invariant ();
383 return next;
384 }
385 template <typename T>
386 void LinkedList<T>::Remove (const ForwardIterator& i)
387 {
389#if qStroika_Foundation_Debug_AssertionsChecked
390 Require (i.fData_ == this); // assure iterator not stale
391#endif
392 Require (not i.AtEnd ());
393 Invariant ();
394 i.Invariant ();
395
396 const Link_* victim = i.fCurrent_;
397
398 /*
399 * At this point we need the prev pointer (so so we can adjust its 'next').
400 * Since the links go in one direction, we must start at the head, and find the item
401 * pointing to the 'victim'.
402 */
403 Link_* prevLink = nullptr;
404 if (this->fHead_ != victim) {
405 auto potentiallyPrevLink = this->fHead_;
406 AssertNotNull (potentiallyPrevLink); // cuz there must be something to remove current
407 for (; potentiallyPrevLink->fNext != victim; potentiallyPrevLink = potentiallyPrevLink->fNext) {
408 AssertNotNull (potentiallyPrevLink); // cuz that would mean victim not in LinkedList!!!
409 }
410 prevLink = potentiallyPrevLink;
411 }
412 Assert (prevLink == nullptr or prevLink->fNext == victim);
413 if (prevLink == nullptr) {
414 Require (this->fHead_ == victim); // If this ever happened, it would mean the argument link to be removed from
415 // this list was not actually in this list! Caller error - serious bug (corruption?)
416 this->fHead_ = victim->fNext;
417 }
418 else {
419 Assert (prevLink->fNext == victim); // because of how we computed prevLink above, this must be true
420 prevLink->fNext = victim->fNext;
421 }
422
423 delete victim;
424 --fLength_;
425 Invariant ();
426 }
427 template <typename T>
428 template <typename EQUALS_COMPARER>
429 void LinkedList<T>::Remove (ArgByValueType<T> item, const EQUALS_COMPARER& equalsComparer)
430 {
431 Debug::AssertExternallySynchronizedChecker::WriteContext declareContext{*this};
432 Invariant ();
433 /*
434 * Base class impl is fine, but doesn't do patching, and doesn't
435 * provide the hooks so I can do the patching from here.
436 *
437 * @todo We may want to correct that (see STL container impl -
438 * returning ptr to next node would do it).
439 */
440 for (ForwardIterator it{this}; not it.AtEnd (); ++it) {
441 if (equalsComparer (*it, item)) {
442 this->Remove (it);
443 break;
444 }
445 }
446 Invariant ();
447 }
448 template <typename T>
449 template <invocable<T> FUNCTION>
450 inline void LinkedList<T>::Apply (FUNCTION&& doToElement) const
451 {
452 AssertExternallySynchronizedChecker::ReadContext declareContext{*this};
453 for (const Link_* i = fHead_; i != nullptr; i = i->fNext) {
454 doToElement (i->fItem);
455 }
456 }
457 template <typename T>
458 template <predicate<T> FUNCTION>
459 inline auto LinkedList<T>::Find (FUNCTION&& firstThat) const -> UnderlyingIteratorRep
460 {
461 AssertExternallySynchronizedChecker::ReadContext declareContext{*this};
462 for (Link_* i = fHead_; i != nullptr; i = i->fNext) {
463 if (firstThat (i->fItem)) {
464 return i;
465 }
466 }
467 return nullptr;
468 }
469 template <typename T>
470 template <typename EQUALS_COMPARER>
471 T* LinkedList<T>::Find (ArgByValueType<T> item, EQUALS_COMPARER&& equalsComparer)
472 {
473 Debug::AssertExternallySynchronizedChecker::WriteContext declareContext{*this}; // lock not shared cuz return mutable ptr
474 for (Link_* i = fHead_; i != nullptr; i = i->fNext) {
475 if (forward<EQUALS_COMPARER> (equalsComparer) (i->fItem, item)) {
476 return &i->fItem;
477 }
478 }
479 return nullptr;
480 }
481 template <typename T>
482 template <typename EQUALS_COMPARER>
483 const T* LinkedList<T>::Find (ArgByValueType<T> item, EQUALS_COMPARER&& equalsComparer) const
484 {
485 AssertExternallySynchronizedChecker::ReadContext declareContext{*this};
486 for (const Link_* i = fHead_; i != nullptr; i = i->fNext) {
487 if (forward<EQUALS_COMPARER> (equalsComparer) (i->fItem, item)) {
488 return &i->fItem;
489 }
490 }
491 return nullptr;
492 }
493 template <typename T>
495 {
497 Invariant ();
498 for (Link_* i = fHead_; i != nullptr;) {
499 Link_* deleteMe = i;
500 i = i->fNext;
501 delete deleteMe;
502 }
503 fHead_ = nullptr;
504 fLength_ = 0;
505 Invariant ();
506 Ensure (empty ());
507 }
508 template <typename T>
509 T LinkedList<T>::GetAt (size_t i) const
510 {
511 AssertExternallySynchronizedChecker::ReadContext declareContext{*this};
512 Require (i >= 0);
513 Require (i < size ());
514 const Link_* cur = fHead_;
515 for (; i != 0; cur = cur->fNext, --i) {
516 AssertNotNull (cur); // cuz i <= fLength
517 }
518 AssertNotNull (cur); // cuz i <= fLength
519 return cur->fItem;
520 }
521 template <typename T>
522 void LinkedList<T>::SetAt (T item, size_t i)
523 {
525 Require (i >= 0);
526 Require (i < size ());
527 Link_* cur = fHead_;
528 for (; i != 0; cur = cur->fNext, --i) {
529 AssertNotNull (cur); // cuz i <= fLength
530 }
531 AssertNotNull (cur); // cuz i <= fLength
532 cur->fItem = item;
533 }
534#if qStroika_Foundation_Debug_AssertionsChecked
535 template <typename T>
536 void LinkedList<T>::Invariant_ () const noexcept
537 {
538#if qStroika_Foundation_Containers_DataStructures_LinkedList_IncludeSlowDebugChecks_
539 AssertExternallySynchronizedChecker::ReadContext declareContext{*this};
540#endif
541 /*
542 * Check we are properly linked together.
543 */
544 size_t n = 0;
545 for (Link_* i = fHead_; i != nullptr; i = i->fNext) {
546 // at least make sure no corrupted links and no infinite loops
547 ++n;
548 }
549 // the cached length must agree with the links, or some mutator failed to maintain it
550 Assert (n == fLength_);
551 }
552#endif
553
554 /*
555 ********************************************************************************
556 ************************* LinkedList<T>::ForwardIterator ***********************
557 ********************************************************************************
558 */
559 template <typename T>
560 constexpr LinkedList<T>::ForwardIterator::ForwardIterator ([[maybe_unused]] const LinkedList* data, UnderlyingIteratorRep startAt) noexcept
561 : fCurrent_{startAt}
562#if qStroika_Foundation_Debug_AssertionsChecked
563 , fData_{data}
564#endif
565 {
566 RequireNotNull (data);
567 }
568 template <typename T>
569 constexpr LinkedList<T>::ForwardIterator::ForwardIterator (const LinkedList* data) noexcept
570 : ForwardIterator{data, (RequireExpression (data != nullptr), data->fHead_)}
571 {
572 RequireNotNull (data);
573 }
574 template <typename T>
575 inline void LinkedList<T>::ForwardIterator::Invariant () const noexcept
576 {
577#if qStroika_Foundation_Debug_AssertionsChecked
578 Invariant_ ();
579#endif
580 }
581 template <typename T>
582 inline LinkedList<T>::ForwardIterator::operator bool () const
583 {
584 return not AtEnd ();
585 }
586 template <typename T>
587 inline bool LinkedList<T>::ForwardIterator::AtEnd () const noexcept
588 {
589 Invariant ();
590 return fCurrent_ == nullptr;
591 }
592 template <typename T>
593 inline auto LinkedList<T>::ForwardIterator::operator++ () noexcept -> ForwardIterator&
594 {
595 Require (not AtEnd ());
596 Invariant ();
597 Assert (fCurrent_ != nullptr);
598 fCurrent_ = fCurrent_->fNext;
599 Invariant ();
600 return *this;
601 }
602 template <typename T>
603 inline auto LinkedList<T>::ForwardIterator::operator++ (int) noexcept -> ForwardIterator
604 {
605 ForwardIterator result = *this;
606 this->operator++ ();
607 return result;
608 }
609 template <typename T>
610 inline T LinkedList<T>::ForwardIterator::operator* () const
611 {
612 Require (not(AtEnd ()));
613 Invariant ();
614 AssertNotNull (fCurrent_);
615 return fCurrent_->fItem;
616 }
617 template <typename T>
618 inline const T* LinkedList<T>::ForwardIterator::operator->() const
619 {
620 Require (not(AtEnd ()));
621 Invariant ();
622 AssertNotNull (fCurrent_);
623 return &fCurrent_->fItem;
624 }
625 template <typename T>
626 size_t LinkedList<T>::ForwardIterator::CurrentIndex (const LinkedList* data) const
627 {
628 Require (not AtEnd ());
629#if qStroika_Foundation_Debug_AssertionsChecked
630 Require (data == fData_);
631 RequireNotNull (fData_);
632#endif
633 RequireNotNull (this->fCurrent_);
634 size_t i = 0;
635 for (const Link_* l = data->fHead_;; l = l->fNext, ++i) {
636 AssertNotNull (l);
637 if (l == fCurrent_) [[unlikely]] {
638 return i;
639 }
640 }
642 return i;
643 }
644 template <typename T>
645 inline auto LinkedList<T>::ForwardIterator::GetUnderlyingIteratorRep () const -> UnderlyingIteratorRep
646 {
647 return fCurrent_;
648 }
649 template <typename T>
650 inline void LinkedList<T>::ForwardIterator::SetUnderlyingIteratorRep (const UnderlyingIteratorRep l)
651 {
652 // MUUST COME FROM THIS LIST
653 // CAN be nullptr
654 fCurrent_ = l;
655 }
656 template <typename T>
657 constexpr void LinkedList<T>::ForwardIterator::AssertDataMatches ([[maybe_unused]] const LinkedList* data) const
658 {
659#if qStroika_Foundation_Debug_AssertionsChecked
660 Require (data == fData_);
661#endif
662 }
663 template <typename T>
664 inline bool LinkedList<T>::ForwardIterator::operator== (const ForwardIterator& rhs) const
665 {
666#if qStroika_Foundation_Debug_AssertionsChecked
667 Require (fData_ == nullptr or rhs.fData_ == nullptr or fData_ == rhs.fData_); // fData_==null for end sentinel case
668#endif
669 return fCurrent_ == rhs.fCurrent_;
670 }
671#if qStroika_Foundation_Debug_AssertionsChecked
672 template <typename T>
673 void LinkedList<T>::ForwardIterator::Invariant_ () const noexcept
674 {
675 }
676#endif
677
678}
#define AssertNotNull(p)
Definition Assertions.h:334
#define RequireNotNull(p)
Definition Assertions.h:348
#define RequireExpression(c)
Definition Assertions.h:268
#define AssertNotReached()
Definition Assertions.h:356
unique_lock< AssertExternallySynchronizedChecker > WriteContext
Instantiate AssertExternallySynchronizedChecker::WriteContext to designate an area of code where prot...