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