Library: improve initial population of ScopedCollection

This is a collection with unique-ownership,
and the ability to create all content objects in ''RAII style''
from the constructor call, using a ''population functor''.

It is often used to maintain a collection of resource handles,
and especially to start a series of threads (for testing).

With the variadic λ-captures from C++20, it is possible to rewrite
the most frequently used population functors to be easier to use.
Notably we can now bind constructor arguments, that will be passed
to each of the created elements — a prominent example being the
thread function when launching several threads. Furthermore,
I added a constructor variant that consumes a Lumiera Iterator.
Adding this kind of generic variants is possible now with C++20,
since overload resolution takes concepts into account.
This commit is contained in:
Fischlurch 2026-05-14 19:30:22 +02:00
parent 3a491855e7
commit 7448b81d49
8 changed files with 358 additions and 175 deletions

View file

@ -88,6 +88,33 @@ test with random numbers::
several threads; each thread should use its own generator then (yet this can be handled sloppy if several threads; each thread should use its own generator then (yet this can be handled sloppy if
the _quality_ of the random number actually does not matter!) the _quality_ of the random number actually does not matter!)
special test setup::
It can be very helpful to use _policy based design_ for some complex evaluation or algorithm,
even in cases where it is clear that the actual usage will only ever involve one specific data type
and configuration. Not only does such a structured approach help with clarifying the design itself,
since the core of the evaluation logic can be abstracted from data representation, but only the
testing and verification of correctness is simplified, as the test can be written to use simple,
human-readable data (strings, symbols), and, furthermore, many kinds of instrumentation can be
included into the test data to gain detailed insight what is happening in the test subject.
verify behaviour sequence::
Use the `lib::EventLog`:
+
- within the test, notable points are marked into the log, possibly with arguments
- after the test, the Event Log can be verified with a search-sequence
- this way it is possible to ensure that some events did happen, and happened
in a very specific order, or that some event did _not_ happen after another event
- the `test::Tracker` defined in 'src/test/tracking-dummy.hpp' will mark all constructor,
destructor and copy events into an embedded Event Log instance. By inheriting or mix-in
from that type, the allocation and instance behaviour of some service can be scrutinised.
verify incidences and concurrency::
Aside from using some generic tracing utility, sometimes it is much easier to setup
a _specifically tailored instrumentation_. This can be achieved with 'incidence-count.hpp'
+
- for instrumentation, the `markEnter()` and `markLeave()` functions need to be invoked
- after the test run, various statistics can be computed and evaluated, including
cumulated time and concurrency for the "cases" marked with enter / leave.
Common Tasks Common Tasks
@ -109,6 +136,12 @@ structured data::
- also useful as output format, both for debugging and because it can be matched against - also useful as output format, both for debugging and because it can be matched against
an expected structure, which is generated with the DSL notation. an expected structure, which is generated with the DSL notation.
- [yellow-background]#(planned)# will be able to map this from/to JSON easily. - [yellow-background]#(planned)# will be able to map this from/to JSON easily.
scoped collection::
unique-ownership collection that holds a sequence of objects in a preallocated fixed
storage block. Notably even non-copyable objects can be managed (which is not possible
with a `std::vector`), and type-erased instances of subclasses can be mixed. Furthermore
there is a constructor variant that allows to populate a `ScopedCollection` in RAII-style.
This can e.g. be used to fire-off a set of Threads or open a set of wrapped connections.
Iterating Iterating
~~~~~~~~~ ~~~~~~~~~
@ -134,6 +167,26 @@ IterSource::
even the production-state can be hooked up into the IterSource instance (with a smart-ptr). even the production-state can be hooked up into the IterSource instance (with a smart-ptr).
This allows e.g. to send a Diff to the UI through the UI-Bus, while the actual generation This allows e.g. to send a Diff to the UI through the UI-Bus, while the actual generation
and application both happen demand-driven or _lazy..._ and application both happen demand-driven or _lazy..._
State Core::
When some algorithm involves a statefull evaluation, it might be helpful to package that into
a _State Core_ container. This is a class that is expected to expose three operations, which
allow to build an iteration on top. The header 'iter-adapter.hpp' provides several adapters
and wrappers, notable `lib::IterStateWrapper` wraps any _state core_ and provides a
Lumiera Iterator interface; likewise, `IterExplorer` heavily relies on that concept and
is able to accept and iterate any state core in a pipeline.
special iterators::
Some additional adaptors are provided as building block so that specific evaluations
can be made to conform with the »Lumiera Iterator« concept
+
- several given iterators can be combined into a iteration over tuples with 'lib/iter-zip.hpp'
- there is also a variation 'lib::izip' that adds a running index into the _zipping operation_
- IterExplorer provides a `flatten()` operation, that can be used to join an iterator-of-iterators
into a single seamless iteration lazily.
- when a STL container also supports _backwards iteration_, then a lumiera iterator can be
created that can _switch direction_ dynamically, with the help of 'iter-cursor.hpp'
- a STL »deque« container can be used both as a stack and a queue; the header 'iter-stack.hpp'
provides an iteration adapter for both usage schemes, so that either the stack or the queue
can be _"discharged"_ as a Lumiera Iterator (possibly with an IterExplorer pipeline)
Formatting Formatting
@ -155,6 +208,25 @@ Formatting
because there are way more capable solutions _out there_ ☺) because there are way more capable solutions _out there_ ☺)
Setup and wiring
~~~~~~~~~~~~~~~~
Generally speaking, we prefer _constructor wiring_ whenever possible, to avoid creating
unnecessary statefulness (as would be the case when connecting dependencies through setters).
This design also favours decomposition into self-contained components with a single purpose,
and simplifies error handling with the help of the RAII pattern.
However, sometimes a piece of code depends on some remote and even global service; in these
cases, the `lib::Depend` framework can be used to access that service via type name. The
actual service can either be created on-demand as a singleton, or it can be installed
explicitly as a service instance. Furthermore, it is possible to "push aside" the
current service instance temporarily and replace it by a _test mock_.
One point to note is that `lib::Depend` is a _monostate_, and not a _singleton_.
The actual service should be written as a free-standing class, that can be instantiated
directly for the purpose of testing.
Language constructs Language constructs
------------------- -------------------
Templates Templates
@ -240,7 +312,7 @@ index and iterate over tuple elements::
This is another example of bridging the compile-time / runtime barrier, using a trampoline. This is another example of bridging the compile-time / runtime barrier, using a trampoline.
Obviously such a solution requires that it is somehow possible to define a generic functor / λ that Obviously such a solution requires that it is somehow possible to define a generic functor / λ that
can accept any of the tuple elements. And, in order to build a runtime index access, we additionally can accept any of the tuple elements. And, in order to build a runtime index access, we additionally
need to convert each of the tuple elements into a _common reconciled type. need to convert each of the tuple elements into a _common reconciled type_.
+ +
- If such a type exists, can be figured out with the help of the `meta::CommonReturn` _metafunction_ - If such a type exists, can be figured out with the help of the `meta::CommonReturn` _metafunction_
- `lib::meta::TupleIdxAdaptor` provides the necessary machinery to define a subscript `operator[]` - `lib::meta::TupleIdxAdaptor` provides the necessary machinery to define a subscript `operator[]`

View file

@ -2,7 +2,7 @@
SCOPED-COLLECTION.hpp - managing a fixed collection of noncopyable polymorphic objects SCOPED-COLLECTION.hpp - managing a fixed collection of noncopyable polymorphic objects
Copyright (C) Copyright (C)
2012, Hermann Vosseler <Ichthyostega@web.de> 2012,2026 Hermann Vosseler <Ichthyostega@web.de>
  **Lumiera** is free software; you can redistribute it and/or modify it   **Lumiera** is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the   under the terms of the GNU General Public License as published by the
@ -13,8 +13,8 @@
/** @file scoped-collection.hpp /** @file scoped-collection.hpp
** Manage a collection of non-copyable polymorphic objects in compact storage. ** Manage a collection of non-copyable polymorphic objects in compact storage.
** This helper supports common situation where a service implementation has to ** This helper supports the common situation where a service implementation has
** manages a fixed collection of implementation related sub-components, treated ** to manage a fixed collection of implementation related sub-components, treated
** with reference semantics. Typically, those objects are used polymorphically, ** with reference semantics. Typically, those objects are used polymorphically,
** and often even with _type erasure_ and added step by step through a builder. ** and often even with _type erasure_ and added step by step through a builder.
** The storage to hold all those child objects is allocated in one chunk ** The storage to hold all those child objects is allocated in one chunk
@ -31,7 +31,7 @@
** the managing scope is alive and able to access them. ** the managing scope is alive and able to access them.
** **
** There are two different usage patterns for populating a ScopedCollection ** There are two different usage patterns for populating a ScopedCollection
** - the "stack style" usage creates an empty container (using the one arg ** - the **stack style** usage creates an empty container (using the one arg
** ctor to specify the maximum size only). The storage capacity to hold ** ctor to specify the maximum size only). The storage capacity to hold
** a number of objects, up to the predefined maximum, is (heap) allocated ** a number of objects, up to the predefined maximum, is (heap) allocated
** immediately at construction of the container, yet no child objects are ** immediately at construction of the container, yet no child objects are
@ -39,11 +39,11 @@
** by invoking #emplaceElement() to create a new element of the default ** by invoking #emplaceElement() to create a new element of the default
** type `I`) or #emplace<Type>(args) to create some subtype. This way, ** type `I`) or #emplace<Type>(args) to create some subtype. This way,
** the container is gradually filled up. ** the container is gradually filled up.
** - the "RAII style" usage strives to create all of the content objects ** - the **RAII-style** usage attempts to create all the contained objects
** right away, immediately after the memory allocation. This usage pattern ** right away, immediately after memory allocation. This usage pattern
** avoids any kind of "lifecycle state". Either the container comes up sane ** avoids any kind of _lifecycle state_. Either the container is in sane
** and fully populated, or the ctor call fails and any already created ** state and fully populated from the outset, or the call to the ctor fails
** objects are discarded. ** and all objects that have already been created are discarded.
** @note intentionally there is no operation to discard individual objects, ** @note intentionally there is no operation to discard individual objects,
** the only option is to #clear() the whole container. ** the only option is to #clear() the whole container.
** @note the container can hold instances of a subclass of the type defined ** @note the container can hold instances of a subclass of the type defined
@ -69,7 +69,7 @@
#include "lib/iter-adapter.hpp" #include "lib/iter-adapter.hpp"
#include <cstddef> #include <cstddef>
#include <type_traits> #include <concepts>
namespace lib { namespace lib {
@ -78,6 +78,7 @@ namespace lib {
using LERR_(INDEX_BOUNDS); using LERR_(INDEX_BOUNDS);
using LERR_(CAPACITY); using LERR_(CAPACITY);
using meta::lum_iter;
/** /**
@ -100,8 +101,8 @@ namespace lib {
public: public:
/** /**
* Storage Frame to hold one Child object. * Storage Frame to hold one Child object.
* The storage will be an heap allocated * The storage will be an heap allocated array
* array of such Wrapper objects. * of such Wrapper objects.
* @note doesn't manage the Child * @note doesn't manage the Child
*/ */
class ElementHolder class ElementHolder
@ -126,7 +127,6 @@ namespace lib {
/** place object of type TY, forwarding ctor arguments */ /** place object of type TY, forwarding ctor arguments */
template<class TY, typename...ARGS> template<class TY, typename...ARGS>
TY& TY&
@ -150,6 +150,9 @@ namespace lib {
clear(); clear();
} }
/** create an empty ScopedCollection with given max capacity
* @remark this ctor variant is intended for _stack-like_ usage
*/
explicit explicit
ScopedCollection (size_t maxElements) ScopedCollection (size_t maxElements)
: level_(0) : level_(0)
@ -164,7 +167,7 @@ namespace lib {
* It gets an ElementHolder& as parameter, and should * It gets an ElementHolder& as parameter, and should
* use this to create an object of suitable I-subclass * use this to create an object of suitable I-subclass
*/ */
template<class CTOR> template<std::invocable<ElementHolder&> CTOR>
ScopedCollection (size_t maxElements, CTOR builder) ScopedCollection (size_t maxElements, CTOR builder)
: level_(0) : level_(0)
, capacity_(maxElements) , capacity_(maxElements)
@ -190,22 +193,44 @@ namespace lib {
populate_by (builder,instance); populate_by (builder,instance);
} }
/** variation of RAII-style: "pull" elements from an iterator.
* Each element will be created directly by invoking either the copy ctor,
* or a suitable 1-argument ctor, depending on the type yielded by the iterator.
* No further elements will be created however, once the iterator is exhausted.
* @note anything in accordance to the Lumiera Forward concept is acceptable.
* This rules out just passing a plain STL iterator (because these can't
* tell for themselves when they're exhausted). Use an suitable iter-adapter
* instead, e.g. by invoking lib::iter_stl::eachElm(stl_container)
*/
template<lum_iter IT>
ScopedCollection (size_t maxElements, IT&& iter)
: level_(0)
, capacity_(maxElements)
, elements_(new ElementHolder[maxElements])
{
pull_from (std::forward<IT> (iter));
}
/* == some pre-defined Builders == */ /* == some pre-defined Builders == */
class FillAll; ///< fills the ScopedCollection with default constructed I-instances /** Builder: ills the given storage slot with a I-instance
* constructed from copies of the given arguments.
* @return builder-λ to pass into ScopeCollection's ctor. */
template<typename...ARGS>
static auto fill (ARGS&&... args);
template<typename TY> /** Builder: fills the given storage slot with a TY-instance
class FillWith; ///< fills the ScopedCollection with default constructed TY-instances * constructed from copies of the given arguments. */
template<typename TY, typename...ARGS>
static auto fillWith (ARGS&&... args);
template<typename IT> /** Builder: fills the given storage slot from invoking the
class PullFrom; ///< fills by copy-constructing values pulled from the iterator IT * provided factory functor, with either an I-element or a
* I-subtype, depending on the functor's result type.
* @note the functor will be copied in the resulting λ */
template<std::invocable<> FUN>
static auto invoke (FUN&& factory);
template<typename IT>
static PullFrom<IT>
pull (IT iter) ///< convenience shortcut to pull from any given Lumiera Forward Iterator
{
return PullFrom<IT> (iter);
}
void void
clear() clear()
@ -247,7 +272,7 @@ namespace lib {
* ElementHolder#create function, which places a new element * ElementHolder#create function, which places a new element
* into the storage frame passed as parameter. * into the storage frame passed as parameter.
*/ */
template<class CTOR> template<std::invocable<ElementHolder&> CTOR>
void void
populate_by (CTOR builder) populate_by (CTOR builder)
try { try {
@ -282,7 +307,42 @@ namespace lib {
catch(...) catch(...)
{ {
WARN (progress, "Failure while populating ScopedCollection. " WARN (progress, "Failure while populating ScopedCollection. "
"All elements will be discarded"); "All elements will be discarded.");
clear();
throw;
}
template<lum_iter IT>
struct _BuildFromIter ///< @internal helper to determine what to build from an iterator
{
using Yield = iter::Yield<IT>;
using Value = meta::ValueTypeBinding<IT>::value_type;
using Type = std::conditional_t<meta::is_Subclass_v<Value, I>, Value, I>;
static_assert (std::is_constructible_v<Type, Yield>
,"Iterator must produce something "
"that can be used to build contents of this container");
};
/** variation of element initialisation,
* creating further elements from the results of an iterator,
* until either the container is filled up or the iterator exhausted.
*/
template<lum_iter IT>
void
pull_from (IT iter)
try {
while (iter and level_ < capacity_)
{
using Element = _BuildFromIter<IT>::Type;
ElementHolder& storageFrame (elements_[level_]);
storageFrame.template create<Element> (*iter);
++level_;
++iter;
} }
catch(...)
{
WARN (progress, "Failure while pulling Iterator into ScopedCollection. "
"All elements will be discarded.");
clear(); clear();
throw; throw;
} }
@ -340,8 +400,8 @@ namespace lib {
size_t size () const { return level_; } size_t size () const { return level_; }
size_t capacity () const { return capacity_; } size_t capacity() const { return capacity_; }
bool empty () const { return 0 == level_; } bool empty() const { return 0 == level_; }
// use in standard range for loops... // use in standard range for loops...
@ -354,7 +414,7 @@ namespace lib {
private: private:
/* ==== Storage: heap allocated array of element buffers ==== */ /* ==== Storage: heap allocated array of element buffers ==== */
typedef std::unique_ptr<ElementHolder[]> ElementStorage; using ElementStorage = std::unique_ptr<ElementHolder[]>;
size_t level_; size_t level_;
size_t capacity_; size_t capacity_;
@ -417,66 +477,66 @@ namespace lib {
/* === Supplement: pre-defined element builders === */ /* === Supplement: pre-defined element builders === */
/** \par usage /** \par usage
* Pass an instance of this builder functor as 2nd parameter * On invocation, this function generates a builder-λ to pass as
* to ScopedCollections's ctor. (an anonymous instance is OK). * the 2nd parameter to ScopedCollections's ctor. When arguments are given,
* these are perfect-forwarded into a copy in the closure, which implies that
* all instances will receive either a copy or const-reference to these values.
* (It is not possible to pass references this way).
* Using this variant of the ctor switches the collection to RAII-style: * Using this variant of the ctor switches the collection to RAII-style:
* It will immediately try to create all the embedded objects, invoking this * It will immediately try to create all the embedded objects, invoking the
* builder functor for each "slot" to hold such an embedded object. Actually, * builder functor for each "slot" to hold such an embedded object. Actually,
* this "slot" is an ElementHolder instance, which provides functions for * this "slot" is an ElementHolder instance, which provides functions for
* placement-creating objects into this embedded buffer. * placement-creating objects into this embedded buffer.
* @note allows also to default-create or copy-create
*/ */
template<class I, size_t siz> template<class I, size_t siz>
class ScopedCollection<I,siz>::FillAll template<typename...ARGS>
{ auto
public: ScopedCollection<I,siz>::fill (ARGS&&... args)
void {
operator() (ScopedCollection<I,siz>::ElementHolder& storage) return [... args = std::forward<ARGS> (args)]
{ (ScopedCollection<I,siz>::ElementHolder& storage)
storage.template create<I>(); {
} storage.template create<I> (args...);
}; };
}
template<class I, size_t siz> template<class I, size_t siz>
template<typename TY> template<typename TY, typename...ARGS>
class ScopedCollection<I,siz>::FillWith auto
{ ScopedCollection<I,siz>::fillWith (ARGS&&... args)
public: {
void return [... args = std::forward<ARGS> (args)]
operator() (ScopedCollection<I,siz>::ElementHolder& storage) (ScopedCollection<I,siz>::ElementHolder& storage)
{ {
storage.template create<TY>(); storage.template create<TY> (args...);
} };
}; }
/** \par usage /**
* This variant allows to "pull" elements from an iterator. * @remark this builder handles the technical aspect of creating elements
* Actually, the collection will try to create each element right away, * into the slots of the ElementHolder storage, so that client code
* by invoking the copy ctor and passing the value yielded by the iterator. * has only to define the constructor argument, which notably can
* @note anything in accordance to the Lumiera Forward Iterator pattern is OK. * also be a subclass of \a I (assuming the storage size suffices)
* This rules out just passing a plain STL iterator (because these can't * @return builder-λ that can be passed into ScopeCollection's ctor.
* tell for themselves when they're exhausted). Use an suitable iter-adapter
* instead, e.g. by invoking lib::iter_stl::eachElm(stl_container)
*/ */
template<class I, size_t siz> template<class I, size_t siz>
template<typename IT> template<std::invocable<> FUN>
class ScopedCollection<I,siz>::PullFrom auto
{ ScopedCollection<I,siz>::invoke (FUN&& factory)
IT iter_; {
using Res = std::invoke_result_t<FUN>;
using Elem = std::conditional_t<meta::is_Subclass_v<Res, I>, Res, I>;
static_assert (std::is_constructible_v<Elem, Res>
,"Iterator must produce something "
"that can be used to build contents of this container");
using ElementType = meta::ValueTypeBinding<IT>::value_type; return [fac = std::forward<FUN> (factory)]
(ScopedCollection<I,siz>::ElementHolder& storage) mutable
public: {
PullFrom (IT source) storage.template create<Elem> (fac());
: iter_(source) };
{ } }
void
operator() (ScopedCollection<I,siz>::ElementHolder& storage)
{
storage.template create<ElementType> (*iter_);
++iter_;
}
};

View file

@ -451,6 +451,8 @@ namespace lib {
* and possibly configure further details, depending on the actual Policy used. * and possibly configure further details, depending on the actual Policy used.
* @remark the primary ThreadLifecycle-ctor accepts such a Launch-instance * @remark the primary ThreadLifecycle-ctor accepts such a Launch-instance
* and invokes a chain of λ-functions collected in the member #launch * and invokes a chain of λ-functions collected in the member #launch
* @note a Launch instance can be built by direct conversion from an arg-less
* thread function (because the ctor is _not_ explicit)
*/ */
struct Launch struct Launch
: util::MoveOnly : util::MoveOnly
@ -571,6 +573,10 @@ namespace lib {
* Primary constructor: Launch the new thread with flexible configuration. * Primary constructor: Launch the new thread with flexible configuration.
* @param launcher a #Launch builder with a λ-chain to configure and * @param launcher a #Launch builder with a λ-chain to configure and
* finally trigger start of the thread * finally trigger start of the thread
* @note The Launch instance can be built by implicit conversion from any
* arg-less invocable; this implies that ThreadLifecycle (and its
* relevant subclasses) can be built without much ado directly
* from a Lambda that defines the thread-function.
*/ */
ThreadLifecycle (Launch launcher) ThreadLifecycle (Launch launcher)
: Policy{launcher.id} : Policy{launcher.id}
@ -644,6 +650,12 @@ namespace lib {
* thread has already copied the arguments and indeed actively started to run. * thread has already copied the arguments and indeed actively started to run.
* @warning The destructor waits for a short grace period of 20ms, but calls * @warning The destructor waits for a short grace period of 20ms, but calls
* `std::terminate` afterwards, should the thread still be active then. * `std::terminate` afterwards, should the thread still be active then.
* @remark creating a Thread instance immediately starts the thread;
* it can be built from
* - any argument-less invocable or lambda (that is invoked in the thread)
* - a string argument for the thread-id, an invocable, further arguments...
* - a member pointer and further arguments; useful for derived classes
* - a ThreadLifecycle::Launch, which allows fine grained configuration
*/ */
class Thread class Thread
: public thread::ThreadLifecycle<thread::PolicyLaunchOnly> : public thread::ThreadLifecycle<thread::PolicyLaunchOnly>

View file

@ -129,19 +129,19 @@ namespace test {
// launch a significant number of threads, // launch a significant number of threads,
// each verifying the same invocation sequence // each verifying the same invocation sequence
lib::ScopedCollection<Thread> threads{NUM_THREADS}; using Threads = lib::ScopedCollection<Thread>;
for (uint i=1; i<=NUM_THREADS; ++i) Threads threads{NUM_THREADS
threads.emplace ([&]{ ,Threads::fill ([&]{
CHECK (i1 == myDummy.invoke()); CHECK (i1 == myDummy.invoke());
sleep_for (100us); sleep_for (100us);
CHECK (i2 == myDummy.invoke()); CHECK (i2 == myDummy.invoke());
sleep_for (100us); sleep_for (100us);
CHECK (i3 == myDummy.invoke()); CHECK (i3 == myDummy.invoke());
}); })};
while (explore(threads).has_any()) while (explore(threads).has_any())
yield(); // wait for all threads to finish yield(); // wait for all threads to finish

View file

@ -379,9 +379,9 @@ namespace test{
{ {
CHECK (0 == Dummy::checksum()); CHECK (0 == Dummy::checksum());
CollD coll (25, CollD::FillAll() ); CollD coll (25, CollD::fill() );
CHECK (!isnil (coll)); CHECK (not isnil (coll));
CHECK (25 == coll.size()); CHECK (25 == coll.size());
CHECK (0 != Dummy::checksum()); CHECK (0 != Dummy::checksum());
@ -393,14 +393,16 @@ namespace test{
} }
/** @test use a "populator" that creates a subclass and binds arguments */
void void
verify_subclassPopulator() verify_subclassPopulator()
{ {
CHECK (0 == Dummy::checksum()); CHECK (0 == Dummy::checksum());
CollD coll (25, CollD::FillWith<SubDummy>() ); const uint arg{42};
CollD coll (25, CollD::fillWith<SubDummy> (arg, 55) );
CHECK (!isnil (coll)); CHECK (not isnil (coll));
CHECK (25 == coll.size()); CHECK (25 == coll.size());
CHECK (0 != Dummy::checksum()); CHECK (0 != Dummy::checksum());
@ -409,29 +411,32 @@ namespace test{
} }
/** @test verify dynamic pulling of elements
* - the first step uses a "populator" that invokes a λ
* - the second step then uses the source container's iterator,
* which happens to be a _Lumiera Forward Iterator_ to pull
* copies into a second container.
* - even while the second container is dimensioned to be larger,
* only the number of elements yielded by the iterator are created.
*/
void void
verify_iteratorPopulator() verify_iteratorPopulator()
{ {
typedef ScopedCollection<uint> CollI; typedef ScopedCollection<uint> CollI;
CollI source (25); CollI source (20, CollI::invoke([i=0] mutable { return i++; }));
for (uint i=0; i < source.capacity(); ++i) // filled with numbers 0..19
source.emplace<uint>(i); // holding the numbers 0..24 CollI coll (30, source.begin()); // this immediately pulls in the first 20 elements
CHECK (not isnil (coll));
CollI coll (20, CollI::pull(source.begin()));
// this immediately pulls in the first 20 elements
CHECK (!isnil (coll));
CHECK (20 == coll.size()); CHECK (20 == coll.size());
CHECK (25 == source.size()); CHECK (20 == source.size());
CHECK (30 == coll.capacity());
for (uint i=0; i < coll.size(); ++i) for (uint i=0; i < coll.size(); ++i)
{ {
CHECK (coll[i] == i ); CHECK (coll[i] == i );
CHECK (coll[i] == source[i]); CHECK (coll[i] == source[i]);
} }
// note: the iterator is assumed to deliver a sufficient amount of elements
VERIFY_ERROR (ITER_EXHAUST, CollI (50, CollI::pull (source.begin())));
} }

View file

@ -19,10 +19,12 @@
#include "test/run.hpp" #include "test/run.hpp"
#include "lib/sync-classlock.hpp" #include "lib/sync-classlock.hpp"
#include "lib/scoped-collection.hpp" #include "lib/scoped-collection.hpp"
#include "lib/iter-explorer.hpp"
#include "lib/thread.hpp" #include "lib/thread.hpp"
using test::Test; using test::Test;
//using vault::ThreadJoinable; //////////////////WIP using lib::explore;
using std::this_thread::yield;
namespace lib { namespace lib {
namespace test { namespace test {
@ -58,29 +60,25 @@ namespace test {
auto gen = buildCappedSubSequence(defaultGen); auto gen = buildCappedSubSequence(defaultGen);
int contended = 0; int contended = 0;
using Thread