From 40d3167d1acec67481eff13af3f0592dc16bb732 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 28 Mar 2026 23:27:23 +0100 Subject: [PATCH] Library: integrate automatic adapters to flatten iterable sources This expands the processing scheme developed thus far, so that an arbitrary nested iterable can be handled, as long as it can be adapted by the generic mechanisms provided by IterExplorer. Notably there is now a type pattern match to distinguish various cases, even spanning the ''rather esoteric'' possibility that an underlying iterator produces STL collections by-value. By examining these special cases, the limitations of the `IteratorExplorer` design were highlighted, and a number of tricky bugs could be fixed. --- src/lib/iter-adapter.hpp | 59 +++ src/lib/iter-explorer.hpp | 94 +++- src/lib/iter-zip.hpp | 2 +- src/lib/itertools.hpp | 69 +++ src/lib/meta/tuple-accessor.hpp | 444 +------------------ src/lib/meta/tuple-concept.hpp | 424 +++--------------- src/lib/meta/tuple-helper.hpp | 398 +---------------- src/lib/stat/csv.hpp | 3 +- src/steam/control/command-op-closure.hpp | 2 +- src/steam/engine/weaving-pattern-builder.hpp | 1 - tests/lib/iter-explorer-test.cpp | 79 ++++ tests/lib/meta/late-bind-instance-test.cpp | 2 +- tests/lib/verb-visitor-dispatch-test.cpp | 2 +- 13 files changed, 377 insertions(+), 1202 deletions(-) diff --git a/src/lib/iter-adapter.hpp b/src/lib/iter-adapter.hpp index 4b4654016..e375802fb 100644 --- a/src/lib/iter-adapter.hpp +++ b/src/lib/iter-adapter.hpp @@ -144,6 +144,7 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/meta/value-type-binding.hpp" #include @@ -686,6 +687,7 @@ namespace lib { * @warning be sure to understand the implications of this setup * - when initialised by reference, the container's contents will be copied * - when move-initialised, the container will be destroyed with this iterator + * - when moved / swapped during iteration, the iterator is invalidated (-> data corruption) * - the container API remains visible (baseclass), which could confuse trait detection */ template @@ -727,6 +729,63 @@ namespace lib { + template + concept subscriptable = requires (CON& con, size_t idx) + { + typename CON::reference; + { con[idx] } -> std::convertible_to; + { con.size() } -> std::convertible_to; + }; + + template + using is_subscriptable = std::bool_constant>>; + + /** + * Adapter to package some container store inline and access it bey sequenced subscript. + * This setup works similar to the ContainerCore, yet has the benefit of being movable. + * @warning using any kind of inline storage in the context of IterExplorer is dangerous. + * Anything beyond the actual iteration state and especially an extended data storage + * might lead to surprising and harmful behaviour, due to the contradiction to the + * common mental image associated with an iterator. These dangers are amplified + * when such embedded data is transformed, filtered or modified by reference. + */ + template + class IdxStoreCore + : public CON + { + size_t i_; + + public: + IdxStoreCore (CON&& container) + : CON(std::forward (container)) + , i_{0} + { } + + IdxStoreCore() = default; + // copy and assignment acceptable (warning!) + + + /* === »state core« protocol API === */ + bool + checkPoint() const + { + return i_ < CON::size(); + } + + decltype(auto) + yield() const + { + return CON::operator[] (i_); + } + + void + iterNext() + { + ++i_; + } + }; + + /** * Decorator-Adapter to make a »*State Core*« iterable as Lumiera Forward Iterator. diff --git a/src/lib/iter-explorer.hpp b/src/lib/iter-explorer.hpp index 688b8ab17..a220efb98 100644 --- a/src/lib/iter-explorer.hpp +++ b/src/lib/iter-explorer.hpp @@ -218,6 +218,14 @@ namespace lib { } }; + + // (forward declarations to allow defining all traits together) + template + class RedressAdapter; + + template + class WrapperAdapter; + }//(End) namespace iter_explorer : basic iterator wrappers @@ -238,6 +246,7 @@ namespace lib { using std::common_type_t; using std::conditional_t; using std::is_convertible; + using std::is_lvalue_reference; using std::remove_reference_t; using meta::is_StateCore; using meta::can_IterForEach; @@ -260,6 +269,13 @@ namespace lib { > { }; + template + struct shall_use_StateCore + : __and_<__not_> + ,is_StateCore + > + { }; + /** @@ -313,6 +329,78 @@ namespace lib { { }; + /** + * @internal Type-selector to adapt a recursive iterable for flattening... + * @tparam SRC the underlying source iterator (which yields a nested iterable as result) + * @tparam RES result type of the source iterator (this is the nested iterable itself) + * @remark to explain the general scheme employed here... + * - we want to add a processing layer with a iter_explorer::Flattener + * - this requires a source-iterator that yields a nested iterable of sorts + * - the source-iterator as such has already been adapted by the [generic mechanism](\ref _DecoratorTraits) + * - however there remains the twist that the _nested result_ (which is some kind of iterable) + * also needs to be adapted, so that it can be handled as Lumiera Forward Iterator... + * - this can be achieved by interspersing an adapter layer _below_ the Flattener, + * so that the result of the underlying iterator will be adapted / casted / wrapped + * - furthermore, we have to distinguish the cases where the underlying iterator produces a value result + * - overall this trait template is instantiated with the type \a SRC of the underlying iterator + * - with the help of the second parameter's default, the \a RES result type of this iterator is determined + * - the specialisation matcher then classifies this result type and determines an adaptation strategy + * - finally, the Flattener is parametrised with this `SrcAdapter` type, but we pass `move(*this) to + * its constructor; since `*this` inherits from \a SRC, the Adapter will pick up and wrap \a SRC + * There are two basic adaptation strategies. First, if the source iterator yields an iterator by reference, + * and we can adapt this iterator on-the-fly (without requiring additional storage), we can just cast the result. + * In all other cases, a setup similar to a Transformer is required: for each source-result (which is in itself + * iterable), a wrapper is constructed into an _inline storage space_ (implemented as \ref lib::ItemWrapper), + * and this wrapper is exposed by-ref as "result value", so that the Flattener can iterate it until exhausted. + */ + template, typename SEL=void> + struct _FlatteningTraits + { + static_assert (!sizeof(SRC), "Can not build Flattener: Unable to figure out how to iterate the nested iterable."); + }; + + template + struct _FlatteningTraits, can_IterForEach >>> + { + using SrcAdapter = meta::RefTraits::value_type; + }; + template + struct _FlatteningTraits>, can_IterForEach >>> + { + using NestedIter = meta::RefTraits::value_type; + using SrcAdapter = iter_explorer::WrapperAdapter; + }; + + template + struct _FlatteningTraits, shall_use_StateCore >>> + { + using CoreRaw = lib::meta::Strip::TypeReferred; + using NestedIter = IterableDecorator; + using SrcAdapter = iter_explorer::RedressAdapter; + }; + template + struct _FlatteningTraits>, shall_use_StateCore >>> + { + using CoreRaw = lib::meta::Strip::TypeReferred; + using NestedIter = IterableDecorator; + using SrcAdapter = iter_explorer::WrapperAdapter; + }; + + template + struct _FlatteningTraits, shall_wrap_STL_Iter >>> + { + using NestedIter = iter_explorer::StlRange; + using SrcAdapter = iter_explorer::WrapperAdapter; + }; + template + struct _FlatteningTraits>, is_subscriptable >>> + { + using StoreRaw = lib::meta::Strip::TypeReferred; + using NestedIter = IterableDecorator>; + using SrcAdapter = iter_explorer::WrapperAdapter; + }; + + /** * helper to derive a suitable common type when expanding children @@ -1605,6 +1693,7 @@ namespace lib { + /** * @internal Decorator for IterExplorer to cut iteration once a predicate ceases to be true. * Similar to Filter, the given functor is adapted as appropriate, yet is required to yield @@ -1949,10 +2038,11 @@ namespace lib { auto flatten() { - using ResCore = iter_explorer::Flattener; + using Adapted = _FlatteningTraits::SrcAdapter; + using ResCore = iter_explorer::Flattener; using ResIter = _DecoratorTraits::SrcIter; - return IterExplorer (ResCore{move(*this)}); + return IterExplorer (ResCore{Adapted{move(*this)}}); } diff --git a/src/lib/iter-zip.hpp b/src/lib/iter-zip.hpp index 1ee811f59..9f34581aa 100644 --- a/src/lib/iter-zip.hpp +++ b/src/lib/iter-zip.hpp @@ -31,7 +31,7 @@ #include "lib/iter-adapter.hpp" #include "lib/iter-explorer.hpp" -#include "lib/meta/tuple-helper.hpp" +#include "lib/meta/tuple-concept.hpp" #include diff --git a/src/lib/itertools.hpp b/src/lib/itertools.hpp index 7cd61baa6..6c952bb94 100644 --- a/src/lib/itertools.hpp +++ b/src/lib/itertools.hpp @@ -803,6 +803,75 @@ namespace lib { + + namespace iter { + + /** + * Iterator adapter to yield a value, + * initialised from the result of an underlying iterator. + * @tparam SRC a Lumiera Forward Iterator to wrap and decorate + * @tparam forceMove if the value shall be initialised by a forced `std::move`; + * otherwise it will be initialised by `std::forward` + * @warning using this adapter may lead to _consuming_ of results; beware! + */ + template + class ValueAdapter + : public SRC + { + static_assert(can_IterForEach::value, "Lumiera Iterator required as source."); + + public: + SRC::value_type + operator*() const + { + using Val = SRC::value_type; + if constexpr (forceMove) + return {move (SRC::operator*())}; + else + return {forward (SRC::operator*())}; + } + + SRC::pointer operator->() const =delete; + + using SRC::SRC; + }; + } // namespace iter + + + /** + * Build an adapter that wraps the given iterator + * and returns by-value, taking a copy from the result + */ + template + auto + copyIt (IT&& iterator) + { + using Iter = meta::RefTraits::value_type; + using CopyAdapter = iter::ValueAdapter; + return CopyAdapter{std::forward (iterator)}; + } + + /** + * Build an adapter that wraps the given iterator + * and returns by-value, thereby **forcibly moving** the result. + * @warning the resulting behaviour breaks the usual assumptions + * regarding iterators, since the first access **consumes** the result. + * Use with extreme care and be sure to understand all ramifications; + * the forced move might even disrupt the underlying source. + */ + template + auto + moveIt (IT&& iterator) + { + using Iter = meta::RefTraits::value_type; + using ForceMoveAdapter = iter::ValueAdapter; + return ForceMoveAdapter{std::forward (iterator)}; + } + + + + + /* === utility functions === */ template diff --git a/src/lib/meta/tuple-accessor.hpp b/src/lib/meta/tuple-accessor.hpp index d6eeb29bc..ad6e8de6a 100644 --- a/src/lib/meta/tuple-accessor.hpp +++ b/src/lib/meta/tuple-accessor.hpp @@ -13,31 +13,13 @@ /** @file tuple-accessor.hpp - ** Metaprogramming with tuples-of-types and the `std::tuple` record. - ** This header complements typelist.hpp and provides a bridge from type sequences - ** to the tuple type provided by the standard library, including traits and - ** helpers to build tuple types from metaprogramming and to pretty-print tuples. + ** Helper templates to access parts of a tuple. ** - ** Notably the handling of tuples is based on a **Concept** `tuple_like`, - ** that is satisfied for any type in compliance with the »tuple protocol«. - ** Together with a [generic accessor][\ref lib::meta::getElm], this allows - ** to handle all _tuple-like_ types uniformly. - ** This solution reaches beyond the scope of the C++ standard, which declines - ** to define such a concept and rather provides an "exposition-only" concept - ** that is limited to a hard-wired selection of library types, yet does not - ** cover custom user provided types able to participate in _structured bindings_. - ** @note Due to that unfortunate limitation of the standard, we're forced to - ** provide our own alternative implementation of `std::apply`. + ** @todo 2026 this is old metaprogramming code, largely obsoleted by newer features of the C++ language. + ** However, since this code is still used in some basic support facilities, it was separated out, + ** so that it can be refactored as time permits. ** - ** Furthermore, a generic iteration construct is provided, to instantiate - ** a generic Lambda for each element of a given tuple, which allows to write - ** generic code »for each tuple element«. - ** - ** @see control::CommandDef usage example ** @see TupleHelper_test - ** @see typelist.hpp - ** @see function.hpp - ** @see generator.hpp ** */ @@ -45,426 +27,14 @@ #ifndef LIB_META_TUPLE_ACCESSOR_H #define LIB_META_TUPLE_ACCESSOR_H -#include "lib/meta/typelist.hpp" -#include "lib/meta/typelist-util.hpp" -#include "lib/meta/typeseq-util.hpp" -#include "lib/meta/variadic-helper.hpp" -#include "lib/meta/util.hpp" - -#include -#include -#include - - -namespace util { // forward declaration - - template - std::string - toString (TY const& val) noexcept; -} +#include "lib/meta/tuple-helper.hpp" +#include "lib/format-obj.hpp" +#include namespace lib { namespace meta { - /** trait to detect tuple types */ - template - struct is_Tuple - : std::false_type - { }; - - template - struct is_Tuple> - : std::true_type - { }; - - template - struct is_Tuple> - : std::true_type - { }; - - using std::remove_cv_t; - using std::is_reference_v; - using std::remove_reference_t; - - - /** @internal building-block: a type supporting the `tuple_size` metafunction */ - template - concept tuple_sized = requires - { - { std::tuple_size::value } -> std::convertible_to; - }; - - - /** @internal building-block: a type where elements can be accessed through a `get` friend function */ - template - concept tuple_adl_accessible = requires(TUP tup) - { - typename std::tuple_element_t; - { get(tup) } -> std::convertible_to&>; - }; - - /** @internal building-block: a type where elements can be accessed through a `get` member function */ - template - concept tuple_mem_accessible = requires(TUP tup) - { - typename std::tuple_element_t; - { tup.template get() } -> std::convertible_to&>; - }; - - template - concept tuple_element_accessible = tuple_mem_accessible or tuple_adl_accessible; - - template - concept tuple_accessible = - tuple_sized and - WithIdxSeq>::andAll([](auto idx) - { - return tuple_element_accessible; - }); - - - /** - * Concept to mark any type compliant to the »tuple protocol« - */ - template - concept tuple_like = not is_reference_v - and tuple_sized> - and tuple_accessible>; - - - /** - * Helper for abstracted / unified access to member elements of any _tuple-like_ - * @remark preferably uses a `get` member function, falling back to a - * free function `get`, which is found by ADL. - */ - template - requires(tuple_like>) - decltype(auto) - getElm (TUP&& tup) - { - using Tup = std::remove_reference_t; - static_assert (0 < std::tuple_size_v); - if constexpr (tuple_mem_accessible) - { - if constexpr (std::is_reference_v) - return tup.template get(); - else - { // return value copy when tuple given as RValue - using Elm = std::tuple_element_t; - Elm elm(tup.template get()); - return elm; - } - } - else - { // ▽▽▽ ADL - using std::get; - return get (std::forward (tup)); - } - } - - - - - namespace { // apply to tuple-like : helpers... - - /** @internal invocation helper similar to C++17 — but preferring a custom `get` impl */ - template - constexpr decltype(auto) - __unpack_and_apply (FUN&& f, TUP&& tup, std::index_sequence) - { - return std::invoke (std::forward (f) - ,getElm (std::forward(tup))... - ); - } - - - /** @internal invoke a metafunction with \a FUN and all element types from the _tuple-like_ \a TUP */ - template class META, class FUN, class TUP> - struct _InvokeMetafunTup - { - using Tupl = std::decay_t; - using Elms = ElmTypes::Seq; - using Args = Prepend::Seq; - using Type = RebindVariadic::Type; - }; - template class META, class FUN, class TUP> - struct _InvokeMetafunTup - { - using Tupl = std::decay_t; - using Elms = typename ElmTypes::template Apply; - using Args = Prepend::Seq; - using Type = RebindVariadic::Type; - }; - - template - inline constexpr bool can_nothrow_invoke_tup = _InvokeMetafunTup::Type::value; - } - - /** - * Replacement for `std::apply` — yet applicable to _tuple-like custom types_. - * For unclear reasons, the standard chooses to reject such custom types, and - * only allows a fixed set of explicitly defined facilities from the Stdlib - * (tuple, pair, array, and some ranges stuff). - * @todo 6/2025 as a first step, this replicates the implementation from C++17; - * the second step would be to constrain this to a concept `tuple_like` - */ - template requires(tuple_like>) - constexpr decltype(auto) - apply (FUN&& f, TUP&& tup) noexcept (can_nothrow_invoke_tup ) - { - using Indices = std::make_index_sequence>>; - - return __unpack_and_apply (std::forward (f) - ,std::forward (tup) - ,Indices{} - ); - } - - - /** - * Tuple iteration: perform some arbitrary operation on each element of a tuple. - * @note the given functor must be generic, since each position of the tuple - * may hold a data element of different type. - * @remark credits to David Vandevoorde (member of C++ committee) for using - * std::apply to unpack the tuple's contents into an argument pack and - * then employ a fold expression with the comma operator. - */ - template requires(tuple_like>) - constexpr void - forEach (TUP&& tuple, FUN fun) - { - lib::meta::apply ([&fun](ELMS&&... elms) - { - (fun (std::forward(elms)), ...); - } - ,std::forward (tuple)); - } - - /** - * Apply some arbitrary function onto all elements of a tuple. - * @return a new tuple constructed from the results of this function - * @note the functor must be generic and able to work with all element types - * @warning pay attention to references; the function can take arguments by-ref - * and manipulate them (side-effect), and it may return references, - * which will be placed as such into the result tuple; furthermore, - * the argument tuple can also be taken as reference... - * @remark The tuple constructor invocation is preceded by a regular argument evaluation, - * which has unspecified evaluation order (even in C++17); _no assumptions_ can be - * made regarding the order the functor will see the source tuple elements. - * Notably this differs from #forEach, where a fold-expression with comma-operator - * is used, which is guaranteed to evaluate from left to right. - */ - template requires(tuple_like>) - constexpr auto - mapEach (TUP&& tuple, FUN fun) - { - return lib::meta::apply ([&fun](ELMS&&... elms) - { //..construct the type explicitly (make_tuple would decay fun result types) - using Tuple = std::tuple(elms))) ...>; - return Tuple (fun (std::forward(elms)) ...); - } - ,std::forward (tuple)); - } - - - - /** - * Specialisation of variadic access for any tuple-like - * @see variadic-helper.hpp - */ - template - struct ElmTypes - { - template - struct Extract; - template - struct Extract> - { - using ElmTypes = Types ...>; - }; - - static constexpr size_t SIZ = std::tuple_size_v; - - using Idx = std::make_index_sequence; - using Seq = Extract::ElmTypes; - using Tup = RebindVariadic::Type; - - template class META> - using Apply = ElmTypes::template Apply; - - template class O> - using Rebind = RebindVariadic::Type; - - template class PRED> - using AndAll = ElmTypes>::template Rebind; - - template class PRED> - using OrAll = ElmTypes>::template Rebind; - }; - - - - - - namespace { // rebinding helper to create std::tuple from a type sequence - - template - struct BuildTupleType - : std::false_type - { }; - - template - struct BuildTupleType> - { - using Type = std::tuple; - }; - - template - struct BuildTupleType> - { - using Seq = Types>::Seq; - using Type = BuildTupleType::Type; - }; - - template<> - struct BuildTupleType - { - using Type = std::tuple<>; - }; - } - - - /** Build a `std::tuple` from types given as type sequence - * @remarks for Lumiera, we deliberately use a dedicated template `Types` - * to mark a type sequence of types as such. This allows to pass such a - * sequence as first-class citizen. The standard library often (ab)uses - * the std::tuple for this purpose, which is an understandable, yet - * inferior design choice. We should always favour dedicated types - * over clever re-use of existing types. - */ - template - using Tuple = BuildTupleType::Type; - - - using std::tuple_size; - using std::tuple_element; - - - - /** match and rebind the type sequence from a tuple */ - template - struct RebindTupleTypes - { - using Seq = Types::Seq; - using List = Seq::List; - }; - template - struct RebindTupleTypes> - { - using Seq = Types::Seq; - using List = Seq::List; - }; - - - - - - - - /** - * Extensible Adapter to construct a distinct tuple from some arbitrary source type. - * This includes the possibility to re-map elements or element positions. - * @tparam TYPES sequence of types to use for the tuple - * @tparam _ElmMapper_ a _template_ to extract each - * constructor argument from the source value. - * On invocation, we'll pick up the source type from the actual ctor argument, - * and then invoke this helper template iteratively for each component of the - * tuple, passing as template arguments - * - the source type, as picked up from the constructor - * - the target tuple type, i.e. `Tuple` - * - the actual index position of the tuple element - * to be initialised through this concrete instantiation. - * @remarks this design has several extension points. Pretty much any conceivable - * initialisation logic can be embodied in the `_ElmMapper_` template. - * Required is that the concrete instance is _constructible_ from the source type - * and _convertible_ to the individual member type of the target tuple it is - * invoked for. Source data _must_ be taken by-value, from the ctor argument. - * @note based on this mechanics, a generic _element extractor_ may be built, - * selecting a (partial) specialisation based on the source type given. - * @see ExtractArg - */ - template< typename TYPES - , template class _ElmMapper_ - > - struct TupleConstructor - : Tuple - { - /** meta-sequence to drive instantiation of the ElmMapper */ - using SequenceIterator = BuildIdxIter::Ascending; - - template - static auto - mapElm (SRC&& init) ///< initialise an instance of the element-mapper - { - return _ElmMapper_ - ,Tuple - , idx - >{std::forward (init)}; - } - - protected: - template - TupleConstructor (SRC&& initVals, IndexSeq) - : Tuple {mapElm (std::forward(initVals)) ...} - { } - - - public: - template - TupleConstructor (SRC&& values) - : TupleConstructor (std::forward(values), SequenceIterator()) - { } - }; - - - /** - * Generic converter to somehow extract values from the "source" - * type to fill and initialise a tuple of given target type. - * @note to be specialised. The concrete specialisation is - * assumed to provide a _member template_ `Access`, - * which in turn picks and converts the value for the n-th - * tuple element. - */ - template - struct ElementExtractor; - - - template - using ExtractArg = ElementExtractor::template Access; - - - /** - * convenience shortcut to build a tuple from some suitable source data. - * For this to work, there needs to be a partial specialisation for - * (\ref ElementExtractor) to deal with the concrete source type given. - * @note we provide such a specialisation for `Record`, which - * allows us to fill an (argument) tuple from a sequence of generic - * data values, with run-time type compatibility check. - * @see tuple-record-init.hpp - */ - template - Tuple - buildTuple (SRC&& values) - { - return TupleConstructor{std::forward (values)}; - } - - - - - - /** diff --git a/src/lib/meta/tuple-concept.hpp b/src/lib/meta/tuple-concept.hpp index 39c2568b4..d7de979fd 100644 --- a/src/lib/meta/tuple-concept.hpp +++ b/src/lib/meta/tuple-concept.hpp @@ -19,14 +19,14 @@ ** helpers to build tuple types from metaprogramming and to pretty-print tuples. ** ** Notably, a `concept tuple_like` is provided here, which is satisfied for any type - ** in compliance with the »tuple protocol«. This solution reaches beyond the scope + ** in compliance with the »[tuple protocol]«. This solution reaches beyond the scope ** of the C++ standard — and in fact opposes the decision of the C++ Committee - ** _not to provide such a concept_, since it can not be defined in full genericity + ** _not to provide such a concept_, as this can not be defined in full genericity ** for any type that can be handled by the _structural bindings of the C++ language. ** ** Since the Lumiera support library is part of a specific application framework, and ** does not intend to be a general purpose library, we prefer a partially complete - ** definition that allows us to subsume and handle custom types under a »Tuple« concept. + ** definition that allows us to subsume and handle custom types under a »Tuple« concept. ** @note Unfortunately the C++ committee did not only decline to provide a tuple_like concept, ** rather they choose to lock-down essential parts of the accompanying library to their ** internal and hard-wired selection of what is considered "tuple-like". This forces us @@ -34,51 +34,24 @@ ** function can be applied to any _tuple-like_ entity. ** @see TupleHelper_test ** @see tuple-helper.hpp extended tuple support functions - ** + ** [tuple protocol]: https://en.cppreference.com/w/cpp/language/structured_binding.html#Case_2:_binding_a_type_implementing_the_tuple_operations */ #ifndef LIB_META_TUPLE_CONCEPT_H #define LIB_META_TUPLE_CONCEPT_H -#include "lib/meta/typelist.hpp" -#include "lib/meta/typelist-util.hpp" -#include "lib/meta/typeseq-util.hpp" -#include "lib/meta/variadic-helper.hpp" #include "lib/meta/util.hpp" +#include "lib/meta/variadic-helper.hpp" -#include -#include +#include #include - - -namespace util { // forward declaration - - template - std::string - toString (TY const& val) noexcept; -} +#include namespace lib { namespace meta { - /** trait to detect tuple types */ - template - struct is_Tuple - : std::false_type - { }; - - template - struct is_Tuple> - : std::true_type - { }; - - template - struct is_Tuple> - : std::true_type - { }; - using std::remove_cv_t; using std::is_reference_v; using std::remove_reference_t; @@ -122,6 +95,9 @@ namespace meta { /** * Concept to mark any type compliant to the »tuple protocol« + * @note a type satisfying this concept can participate in _structured bindings_, + * yet not every type that can be used in such bindings will fulfil this concept, + * since structured bindings can also be applied to _some POD like_ data records. */ template concept tuple_like = not is_reference_v @@ -162,6 +138,46 @@ namespace meta { + /** + * Specialisation of variadic access for any tuple-like + * @see variadic-helper.hpp + */ + template + struct ElmTypes + { + template + struct Extract; + template + struct Extract> + { + using ElmTypes = Types ...>; + }; + + static constexpr size_t SIZ = std::tuple_size_v; + + using Idx = std::make_index_sequence; + using Seq = Extract::ElmTypes; + using Tup = RebindVariadic::Type; + + template class META> + using Apply = ElmTypes::template Apply; + + template class O> + using Rebind = RebindVariadic::Type; + + template class PRED> + using AndAll = ElmTypes>::template Rebind; + + template class PRED> + using OrAll = ElmTypes>::template Rebind; + }; + + + + + + + namespace { // apply to tuple-like : helpers... /** @internal invocation helper similar to C++17 — but preferring a custom `get` impl */ @@ -263,345 +279,5 @@ namespace meta { ,std::forward (tuple)); } - - - /** - * Specialisation of variadic access for any tuple-like - * @see variadic-helper.hpp - */ - template - struct ElmTypes - { - template - struct Extract; - template - struct Extract> - { - using ElmTypes = Types ...>; - }; - - static constexpr size_t SIZ = std::tuple_size_v; - - using Idx = std::make_index_sequence; - using Seq = Extract::ElmTypes; - using Tup = RebindVariadic::Type; - - template class META> - using Apply = ElmTypes::template Apply; - - template class O> - using Rebind = RebindVariadic::Type; - - template class PRED> - using AndAll = ElmTypes>::template Rebind; - - template class PRED> - using OrAll = ElmTypes>::template Rebind; - }; - - - - - - namespace { // rebinding helper to create std::tuple from a type sequence - - template - struct BuildTupleType - : std::false_type - { }; - - template - struct BuildTupleType> - { - using Type = std::tuple; - }; - - template - struct BuildTupleType> - { - using Seq = Types>::Seq; - using Type = BuildTupleType::Type; - }; - - template<> - struct BuildTupleType - { - using Type = std::tuple<>; - }; - } - - - /** Build a `std::tuple` from types given as type sequence - * @remarks for Lumiera, we deliberately use a dedicated template `Types` - * to mark a type sequence of types as such. This allows to pass such a - * sequence as first-class citizen. The standard library often (ab)uses - * the std::tuple for this purpose, which is an understandable, yet - * inferior design choice. We should always favour dedicated types - * over clever re-use of existing types. - */ - template - using Tuple = BuildTupleType::Type; - - - using std::tuple_size; - using std::tuple_element; - - - - /** match and rebind the type sequence from a tuple */ - template - struct RebindTupleTypes - { - using Seq = Types::Seq; - using List = Seq::List; - }; - template - struct RebindTupleTypes> - { - using Seq = Types::Seq; - using List = Seq::List; - }; - - - - - - - - /** - * Extensible Adapter to construct a distinct tuple from some arbitrary source type. - * This includes the possibility to re-map elements or element positions. - * @tparam TYPES sequence of types to use for the tuple - * @tparam _ElmMapper_ a _template_ to extract each - * constructor argument from the source value. - * On invocation, we'll pick up the source type from the actual ctor argument, - * and then invoke this helper template iteratively for each component of the - * tuple, passing as template arguments - * - the source type, as picked up from the constructor - * - the target tuple type, i.e. `Tuple` - * - the actual index position of the tuple element - * to be initialised through this concrete instantiation. - * @remarks this design has several extension points. Pretty much any conceivable - * initialisation logic can be embodied in the `_ElmMapper_` template. - * Required is that the concrete instance is _constructible_ from the source type - * and _convertible_ to the individual member type of the target tuple it is - * invoked for. Source data _must_ be taken by-value, from the ctor argument. - * @note based on this mechanics, a generic _element extractor_ may be built, - * selecting a (partial) specialisation based on the source type given. - * @see ExtractArg - */ - template< typename TYPES - , template class _ElmMapper_ - > - struct TupleConstructor - : Tuple - { - /** meta-sequence to drive instantiation of the ElmMapper */ - using SequenceIterator = BuildIdxIter::Ascending; - - template - static auto - mapElm (SRC&& init) ///< initialise an instance of the element-mapper - { - return _ElmMapper_ - ,Tuple - , idx - >{std::forward (init)}; - } - - protected: - template - TupleConstructor (SRC&& initVals, IndexSeq) - : Tuple {mapElm (std::forward(initVals)) ...} - { } - - - public: - template - TupleConstructor (SRC&& values) - : TupleConstructor (std::forward(values), SequenceIterator()) - { } - }; - - - /** - * Generic converter to somehow extract values from the "source" - * type to fill and initialise a tuple of given target type. - * @note to be specialised. The concrete specialisation is - * assumed to provide a _member template_ `Access`, - * which in turn picks and converts the value for the n-th - * tuple element. - */ - template - struct ElementExtractor; - - - template - using ExtractArg = ElementExtractor::template Access; - - - /** - * convenience shortcut to build a tuple from some suitable source data. - * For this to work, there needs to be a partial specialisation for - * (\ref ElementExtractor) to deal with the concrete source type given. - * @note we provide such a specialisation for `Record`, which - * allows us to fill an (argument) tuple from a sequence of generic - * data values, with run-time type compatibility check. - * @see tuple-record-init.hpp - */ - template - Tuple - buildTuple (SRC&& values) - { - return TupleConstructor{std::forward (values)}; - } - - - - - - - - - /** - * Decorating a tuple type with auxiliary data access operations. - * This helper template builds up a subclass of the given TUP (base) type - * (which is assumed to be a Tuple or at least need to be copy constructible - * from `Tuple` ). The purpose is to use the Tuple as storage record, but - * to add a layer of access functions, which in turn might rely on the exact - * type of the individual elements within the Tuple. To achieve this, for each - * type within the Tuple, the TUP type is decorated with an instance of the - * template passed in as template template parameter _X_. Each of these - * decorating instances is provided with an index number, allowing to - * access "his" specific element within the underlying tuple. - * - * The decorating template _X_ need to take its own base class as template - * parameter. Typically, operations on _X_ will be defined in a recursive fashion, - * calling down into this templated base class. To support this, an instantiation - * of _X_ with the empty type sequence is generated for detecting recursion end - * (built as innermost decorator, i.e. the immediate subclass of TUP) - */ - template - < template class _X_ ///< user provided template - , typename TYPES ///< Sequence of types to use within the Accessor - , class TUP =Tuple ///< the tuple type to build on - , uint i = 0 ///< tuple element index counter - > - class BuildTupleAccessor - { - // prepare recursion... - using Head = Split::Head; - using Tail = Split::Tail; - using NextBuilder = BuildTupleAccessor<_X_, Tail,TUP, i+1>; - using NextAccessor = NextBuilder::Product; - public: - - /** type of the product created by this template. - * Will be a subclass of TUP */ - using Product = _X_< Head // the type to use for this accessor - , NextAccessor // the base type to inherit from - , TUP // the tuple type we build upon - , i // current element index - >; - }; - - - template - < template class _X_ - , class TUP - , uint i - > - class BuildTupleAccessor< _X_, Types<>, TUP, i> - { - public: - using Product = _X_; // Note: i == tuple size - }; - - - - /** - * Helper to dump tuple contents. - * Defined to act as "Accessor" for BuildTupleAccessor, this helper template - * allows to create a recursive operation to invoke string conversion on - * all elements within any given tuple. - */ - template - < typename TY - , class BASE - , class TUP - , uint idx - > - struct TupleElementDisplayer - : BASE - { - using BASE::BASE; - - std::string - dump (std::string const& prefix ="(") const - { - return BASE::dump (prefix + util::toString(std::get(*this))+","); - } - }; - - template - struct TupleElementDisplayer - : TUP - { - TupleElementDisplayer (TUP const& tup) - : TUP(tup) - { } - - std::string - dump (std::string const& prefix ="(") const - { - if (1 < prefix.length()) - // remove the trailing comma - return prefix.substr (0, prefix.length()-1) +")"; - else - return prefix+")"; - } - }; - - - /** - * convenience function to dump a given tuple's contents. - * Using the BuildTupleAccessor, we layer a stack of Instantiations of - * the TupleElementDisplayer temporarily on top of the given tuple, - * just to invoke a recursive call chain through these layers - * and get a string representation of each element in the - * tuple. - */ - template - inline std::string - dump (std::tuple const& tuple) - { - using BuildAccessor = BuildTupleAccessor>; - using Displayer = BuildAccessor::Product ; - - return static_cast (tuple) - .dump(); - } - - - }} // namespace lib::meta - - -// add a specialisation to enable tuple string conversion -namespace util { - - template - struct StringConv> - { - static std::string - invoke (std::tuple const& tuple) noexcept - try { - return "«"+typeStr(tuple) - + "»──" + lib::meta::dump (tuple); - } - catch(...) { return FAILURE_INDICATOR; } - }; - - -} // namespace util #endif /*LIB_META_TUPLE_CONCEPT_H*/ diff --git a/src/lib/meta/tuple-helper.hpp b/src/lib/meta/tuple-helper.hpp index 50c4d738c..25537af54 100644 --- a/src/lib/meta/tuple-helper.hpp +++ b/src/lib/meta/tuple-helper.hpp @@ -19,7 +19,7 @@ ** helpers to build tuple types from metaprogramming and to pretty-print tuples. ** ** Notably the handling of tuples is based on a **Concept** `tuple_like`, - ** that is satisfied for any type in compliance with the »tuple protocol«. + ** that is satisfied for any type in compliance with the »[tuple protocol]«. ** Together with a [generic accessor][\ref lib::meta::getElm], this allows ** to handle all _tuple-like_ types uniformly. ** This solution reaches beyond the scope of the C++ standard, which declines @@ -33,6 +33,7 @@ ** a generic Lambda for each element of a given tuple, which allows to write ** generic code »for each tuple element«. ** + ** [tuple protocol]: https://en.cppreference.com/w/cpp/language/structured_binding.html#Case_2:_binding_a_type_implementing_the_tuple_operations ** @see control::CommandDef usage example ** @see TupleHelper_test ** @see typelist.hpp @@ -45,10 +46,12 @@ #ifndef LIB_META_TUPLE_HELPER_H #define LIB_META_TUPLE_HELPER_H +#include "lib/integral.hpp" +#include "lib/meta/tuple-concept.hpp" +#include "lib/meta/variadic-helper.hpp" +#include "lib/meta/typeseq-util.hpp" #include "lib/meta/typelist.hpp" #include "lib/meta/typelist-util.hpp" -#include "lib/meta/typeseq-util.hpp" -#include "lib/meta/variadic-helper.hpp" #include "lib/meta/util.hpp" #include @@ -56,14 +59,6 @@ #include -namespace util { // forward declaration - - template - std::string - toString (TY const& val) noexcept; -} - - namespace lib { namespace meta { @@ -83,225 +78,6 @@ namespace meta { : std::true_type { }; - using std::remove_cv_t; - using std::is_reference_v; - using std::remove_reference_t; - - - /** @internal building-block: a type supporting the `tuple_size` metafunction */ - template - concept tuple_sized = requires - { - { std::tuple_size::value } -> std::convertible_to; - }; - - - /** @internal building-block: a type where elements can be accessed through a `get` friend function */ - template - concept tuple_adl_accessible = requires(TUP tup) - { - typename std::tuple_element_t; - { get(tup) } -> std::convertible_to&>; - }; - - /** @internal building-block: a type where elements can be accessed through a `get` member function */ - template - concept tuple_mem_accessible = requires(TUP tup) - { - typename std::tuple_element_t; - { tup.template get() } -> std::convertible_to&>; - }; - - template - concept tuple_element_accessible = tuple_mem_accessible or tuple_adl_accessible; - - template - concept tuple_accessible = - tuple_sized and - WithIdxSeq>::andAll([](auto idx) - { - return tuple_element_accessible; - }); - - - /** - * Concept to mark any type compliant to the »tuple protocol« - */ - template - concept tuple_like = not is_reference_v - and tuple_sized> - and tuple_accessible>; - - - /** - * Helper for abstracted / unified access to member elements of any _tuple-like_ - * @remark preferably uses a `get` member function, falling back to a - * free function `get`, which is found by ADL. - */ - template - requires(tuple_like>) - decltype(auto) - getElm (TUP&& tup) - { - using Tup = std::remove_reference_t; - static_assert (0 < std::tuple_size_v); - if constexpr (tuple_mem_accessible) - { - if constexpr (std::is_reference_v) - return tup.template get(); - else - { // return value copy when tuple given as RValue - using Elm = std::tuple_element_t; - Elm elm(tup.template get()); - return elm; - } - } - else - { // ▽▽▽ ADL - using std::get; - return get (std::forward (tup)); - } - } - - - - - namespace { // apply to tuple-like : helpers... - - /** @internal invocation helper similar to C++17 — but preferring a custom `get` impl */ - template - constexpr decltype(auto) - __unpack_and_apply (FUN&& f, TUP&& tup, std::index_sequence) - { - return std::invoke (std::forward (f) - ,getElm (std::forward(tup))... - ); - } - - - /** @internal invoke a metafunction with \a FUN and all element types from the _tuple-like_ \a TUP */ - template class META, class FUN, class TUP> - struct _InvokeMetafunTup - { - using Tupl = std::decay_t; - using Elms = ElmTypes::Seq; - using Args = Prepend::Seq; - using Type = RebindVariadic::Type; - }; - template class META, class FUN, class TUP> - struct _InvokeMetafunTup - { - using Tupl = std::decay_t; - using Elms = typename ElmTypes::template Apply; - using Args = Prepend::Seq; - using Type = RebindVariadic::Type; - }; - - template - inline constexpr bool can_nothrow_invoke_tup = _InvokeMetafunTup::Type::value; - } - - /** - * Replacement for `std::apply` — yet applicable to _tuple-like custom types_. - * For unclear reasons, the standard chooses to reject such custom types, and - * only allows a fixed set of explicitly defined facilities from the Stdlib - * (tuple, pair, array, and some ranges stuff). - * @todo 6/2025 as a first step, this replicates the implementation from C++17; - * the second step would be to constrain this to a concept `tuple_like` - */ - template requires(tuple_like>) - constexpr decltype(auto) - apply (FUN&& f, TUP&& tup) noexcept (can_nothrow_invoke_tup ) - { - using Indices = std::make_index_sequence>>; - - return __unpack_and_apply (std::forward (f) - ,std::forward (tup) - ,Indices{} - ); - } - - - /** - * Tuple iteration: perform some arbitrary operation on each element of a tuple. - * @note the given functor must be generic, since each position of the tuple - * may hold a data element of different type. - * @remark credits to David Vandevoorde (member of C++ committee) for using - * std::apply to unpack the tuple's contents into an argument pack and - * then employ a fold expression with the comma operator. - */ - template requires(tuple_like>) - constexpr void - forEach (TUP&& tuple, FUN fun) - { - lib::meta::apply ([&fun](ELMS&&... elms) - { - (fun (std::forward(elms)), ...); - } - ,std::forward (tuple)); - } - - /** - * Apply some arbitrary function onto all elements of a tuple. - * @return a new tuple constructed from the results of this function - * @note the functor must be generic and able to work with all element types - * @warning pay attention to references; the function can take arguments by-ref - * and manipulate them (side-effect), and it may return references, - * which will be placed as such into the result tuple; furthermore, - * the argument tuple can also be taken as reference... - * @remark The tuple constructor invocation is preceded by a regular argument evaluation, - * which has unspecified evaluation order (even in C++17); _no assumptions_ can be - * made regarding the order the functor will see the source tuple elements. - * Notably this differs from #forEach, where a fold-expression with comma-operator - * is used, which is guaranteed to evaluate from left to right. - */ - template requires(tuple_like>) - constexpr auto - mapEach (TUP&& tuple, FUN fun) - { - return lib::meta::apply ([&fun](ELMS&&... elms) - { //..construct the type explicitly (make_tuple would decay fun result types) - using Tuple = std::tuple(elms))) ...>; - return Tuple (fun (std::forward(elms)) ...); - } - ,std::forward (tuple)); - } - - - - /** - * Specialisation of variadic access for any tuple-like - * @see variadic-helper.hpp - */ - template - struct ElmTypes - { - template - struct Extract; - template - struct Extract> - { - using ElmTypes = Types ...>; - }; - - static constexpr size_t SIZ = std::tuple_size_v; - - using Idx = std::make_index_sequence; - using Seq = Extract::ElmTypes; - using Tup = RebindVariadic::Type; - - template class META> - using Apply = ElmTypes::template Apply; - - template class O> - using Rebind = RebindVariadic::Type; - - template class PRED> - using AndAll = ElmTypes>::template Rebind; - - template class PRED> - using OrAll = ElmTypes>::template Rebind; - }; @@ -394,8 +170,8 @@ namespace meta { * selecting a (partial) specialisation based on the source type given. * @see ExtractArg */ - template< typename TYPES - , template class _ElmMapper_ + template class _ElmMapper_ > struct TupleConstructor : Tuple @@ -403,16 +179,18 @@ namespace meta { /** meta-sequence to drive instantiation of the ElmMapper */ using SequenceIterator = BuildIdxIter::Ascending; - template + template + using ElmMapperFor = _ElmMapper_, Tuple, idx>; + + template static auto mapElm (SRC&& init) ///< initialise an instance of the element-mapper { - return _ElmMapper_ - ,Tuple - , idx - >{std::forward (init)}; + return ElmMapperFor{std::forward (init)}; } + + protected: template TupleConstructor (SRC&& initVals, IndexSeq) @@ -459,153 +237,7 @@ namespace meta { { return TupleConstructor{std::forward (values)}; } - - - - - - - - - /** - * Decorating a tuple type with auxiliary data access operations. - * This helper template builds up a subclass of the given TUP (base) type - * (which is assumed to be a Tuple or at least need to be copy constructible - * from `Tuple` ). The purpose is to use the Tuple as storage record, but - * to add a layer of access functions, which in turn might rely on the exact - * type of the individual elements within the Tuple. To achieve this, for each - * type within the Tuple, the TUP type is decorated with an instance of the - * template passed in as template template parameter _X_. Each of these - * decorating instances is provided with an index number, allowing to - * access "his" specific element within the underlying tuple. - * - * The decorating template _X_ need to take its own base class as template - * parameter. Typically, operations on _X_ will be defined in a recursive fashion, - * calling down into this templated base class. To support this, an instantiation - * of _X_ with the empty type sequence is generated for detecting recursion end - * (built as innermost decorator, i.e. the immediate subclass of TUP) - */ - template - < template class _X_ ///< user provided template - , typename TYPES ///< Sequence of types to use within the Accessor - , class TUP =Tuple ///< the tuple type to build on - , uint i = 0 ///< tuple element index counter - > - class BuildTupleAccessor - { - // prepare recursion... - using Head = Split::Head; - using Tail = Split::Tail; - using NextBuilder = BuildTupleAccessor<_X_, Tail,TUP, i+1>; - using NextAccessor = NextBuilder::Product; - public: - - /** type of the product created by this template. - * Will be a subclass of TUP */ - using Product = _X_< Head // the type to use for this accessor - , NextAccessor // the base type to inherit from - , TUP // the tuple type we build upon - , i // current element index - >; - }; - - - template - < template class _X_ - , class TUP - , uint i - > - class BuildTupleAccessor< _X_, Types<>, TUP, i> - { - public: - using Product = _X_; // Note: i == tuple size - }; - - - - /** - * Helper to dump tuple contents. - * Defined to act as "Accessor" for BuildTupleAccessor, this helper template - * allows to create a recursive operation to invoke string conversion on - * all elements within any given tuple. - */ - template - < typename TY - , class BASE - , class TUP - , uint idx - > - struct TupleElementDisplayer - : BASE - { - using BASE::BASE; - - std::string - dump (std::string const& prefix ="(") const - { - return BASE::dump (prefix + util::toString(std::get(*this))+","); - } - }; - - template - struct TupleElementDisplayer - : TUP - { - TupleElementDisplayer (TUP const& tup) - : TUP(tup) - { } - - std::string - dump (std::string const& prefix ="(") const - { - if (1 < prefix.length()) - // remove the trailing comma - return prefix.substr (0, prefix.length()-1) +")"; - else - return prefix+")"; - } - }; - - - /** - * convenience function to dump a given tuple's contents. - * Using the BuildTupleAccessor, we layer a stack of Instantiations of - * the TupleElementDisplayer temporarily on top of the given tuple, - * just to invoke a recursive call chain through these layers - * and get a string representation of each element in the - * tuple. - */ - template - inline std::string - dump (std::tuple const& tuple) - { - using BuildAccessor = BuildTupleAccessor>; - using Displayer = BuildAccessor::Product ; - - return static_cast (tuple) - .dump(); - } - }} // namespace lib::meta - - -// add a specialisation to enable tuple string conversion -namespace util { - - template - struct StringConv> - { - static std::string - invoke (std::tuple const& tuple) noexcept - try { - return "«"+typeStr(tuple) - + "»──" + lib::meta::dump (tuple); - } - catch(...) { return FAILURE_INDICATOR; } - }; - - -} // namespace util #endif /*LIB_META_TUPLE_HELPER_H*/ diff --git a/src/lib/stat/csv.hpp b/src/lib/stat/csv.hpp index c55680cd0..21a05bf97 100644 --- a/src/lib/stat/csv.hpp +++ b/src/lib/stat/csv.hpp @@ -40,8 +40,9 @@ #include "lib/error.hpp" #include "lib/null-value.hpp" -#include "lib/meta/tuple-helper.hpp" +#include "lib/meta/tuple-concept.hpp" #include "lib/format-string.hpp" +#include "lib/format-obj.hpp" #include "lib/regex.hpp" #include diff --git a/src/steam/control/command-op-closure.hpp b/src/steam/control/command-op-closure.hpp index 6ade2f271..19bf0201f 100644 --- a/src/steam/control/command-op-closure.hpp +++ b/src/steam/control/command-op-closure.hpp @@ -29,7 +29,7 @@ #include "lib/meta/function.hpp" #include "lib/meta/function-closure.hpp" -#include "lib/meta/tuple-helper.hpp" +#include "lib/meta/tuple-accessor.hpp" #include "lib/meta/tuple-record-init.hpp" #include "steam/control/command-closure.hpp" #include "steam/control/argument-erasure.hpp" diff --git a/src/steam/engine/weaving-pattern-builder.hpp b/src/steam/engine/weaving-pattern-builder.hpp index 0d624a441..eb2937a4e 100644 --- a/src/steam/engine/weaving-pattern-builder.hpp +++ b/src/steam/engine/weaving-pattern-builder.hpp @@ -138,7 +138,6 @@ #include "steam/engine/media-weaving-pattern.hpp" #include "vault/mem/buffer-provider.hpp" #include "lib/meta/tuple-closure.hpp" -#include "lib/meta/tuple-helper.hpp" #include "lib/format-string.hpp" #include "lib/iter-zip.hpp" #include "lib/util.hpp" diff --git a/tests/lib/iter-explorer-test.cpp b/tests/lib/iter-explorer-test.cpp index 84faa31e4..836d554db 100644 --- a/tests/lib/iter-explorer-test.cpp +++ b/tests/lib/iter-explorer-test.cpp @@ -673,6 +673,10 @@ namespace test{ * this amounts to _»flattening«_ a single level of nesting. When linked into the * pipeline behind a transforming function, the combined processing pattern is * similar to the `flatMap(fun)` or `M >= fun` operations known from Monad theory. + * @note the special twist with this implementation is how to adapt various + * flavours of _nested iterables_ that are produced by the preceding pipeline, + * and will usually be exposed by-reference, yet can in rare corner cases + * also be produced by-value, necessitating the use of a WrapperAdapter. */ void verify_flattenNestedIterator() @@ -689,6 +693,36 @@ namespace test{ == "5-4-3-2-1-8-7-6-5"_expect); + // STL-iterable with a sequence of StateCore elements + auto nested_2 = array{CountDown{3}, CountDown{7,5}, CountDown{2}}; + + CHECK (materialise ( + explore(nested_2) + .flatten() + ) + == "3-2-1-7-6-2-1"_expect); + + + // STL-iterable with a sequence of STL containers + auto nested_3 = array{array{21,13}, array{8,5}, array{3,2}}; + + CHECK (materialise ( + explore(nested_3) + .flatten() + ) + == "21-13-8-5-3-2"_expect); + + + // Iterator pipeline that yields STL containers by value-copy + auto nested_4 = copyIt (explore (nested_3)); + + CHECK (materialise ( + explore(nested_4) + .flatten() + ) + == "21-13-8-5-3-2"_expect); + + // Investigate the types used for adapting the iterator pipeline.... auto baseType = [](auto& src){ using BasePipeline = _DecoratorTraits::SrcIter; @@ -698,12 +732,57 @@ namespace test{ using NestedResult = iter::Yield; return showType(); }; + auto adapter = [](auto& src){ using BasePipeline = _DecoratorTraits::SrcIter; + using AdaptedIter = _FlatteningTraits::SrcAdapter; + return showType(); + }; // the base pipeline fed into the Flattener.. CHECK (baseType(nested_1) == "iter_explorer::StlRange >, 2ul>&>"_expect ); + CHECK (baseType(nested_2) == "iter_explorer::StlRange&>"_expect ); + CHECK (baseType(nested_3) == "iter_explorer::StlRange, 3ul>&>"_expect ); + CHECK (baseType(nested_4) == "iter::ValueAdapter, 3ul>&> > >, false>"_expect ); // the nested (iterable) result type produced by this base pipeline... CHECK (nestedIT(nested_1) == "IterableDecorator >&"_expect ); + CHECK (nestedIT(nested_2) == "CountDown&"_expect ); + CHECK (nestedIT(nested_3) == "array&"_expect ); + CHECK (nestedIT(nested_4) == "array"_expect ); + + // In case of nested_1, no adaptation is necessary, + // as this pipeline already yields a nested Lumiera Iterator + CHECK ( adapter(nested_1) == baseType(nested_1) ); + + // The pipeline for nested_2 yields a sequence of references to »StateCore« objects; + // it is adapted by "projecting" (casting on-the-fly) an IterableDecorator to adapt this StateCore (CountDown) + CHECK ( adapter(nested_2) == "iter_explorer::RedressAdapter&>" + ", IterableDecorator >"_expect ); + + // For the nested_3 case the pipeline yields a sequence of references to STL containers (here: `std::array`) + // This requires a WrapperAdapter, that builds a `StlRange` adapter within its embedded storage, for each element anew... + CHECK ( adapter(nested_3) == "iter_explorer::WrapperAdapter, 3ul>&>" + ", iter_explorer::StlRange&> >"_expect ); + + // Finally, the nested_4 is an contrived example, where the underlying pipeline yields + // a sequence of STL container *values*. Notably this implies that each "pull" from the + // underlying iterator yields a fresh copy, which must be retrieved and packaged into + // the WrapperAdapter nested storage, where it is also wrapped into an IdxStoreCore, + // so that the embedded STL container can be accessed with the Lumiera iterator scheme. + // NOTE: the IdxStoreCore is used, since it embeds a *current index*, so that the iteration + // can be continued seamlessly after the iterator pipeline has been moved to another location... + CHECK ( adapter(nested_4) == "iter_explorer::WrapperAdapter, 3ul>&> > >" + ", false>" + ", IterableDecorator > > >"_expect ); + + // Document the nested adapter structure + // for the complete pipeline used in case-4... + using Pipeline4 = decltype( explore(nested_4).flatten() ); + + CHECK (showType() == "IterExplorer<" + "IterableDecorator, 3ul>&> > >, false>" + ", IterableDecorator > > > > > > >"_expect); } diff --git a/tests/lib/meta/late-bind-instance-test.cpp b/tests/lib/meta/late-bind-instance-test.cpp index 9abdbd1fb..071cffc2c 100644 --- a/tests/lib/meta/late-bind-instance-test.cpp +++ b/tests/lib/meta/late-bind-instance-test.cpp @@ -19,8 +19,8 @@ #include "test/run.hpp" #include "test/test-helper.hpp" #include "test/tracking-dummy.hpp" +#include "lib/meta/tuple-accessor.hpp" #include "lib/meta/function.hpp" -#include "lib/meta/tuple-helper.hpp" #include "lib/format-cout.hpp" #include "lib/format-util.hpp" #include "lib/util.hpp" diff --git a/tests/lib/verb-visitor-dispatch-test.cpp b/tests/lib/verb-visitor-dispatch-test.cpp index 39265d5a1..ed778a6ab 100644 --- a/tests/lib/verb-visitor-dispatch-test.cpp +++ b/tests/lib/verb-visitor-dispatch-test.cpp @@ -22,7 +22,7 @@ #include "lib/format-string.hpp" #include "lib/format-cout.hpp" #include "lib/format-util.hpp" -#include "lib/meta/tuple-helper.hpp" +#include "lib/meta/tuple-accessor.hpp" #include "lib/iter-explorer.hpp" #include