diff --git a/src/lib/iter-adapter.hpp b/src/lib/iter-adapter.hpp index c2705a309..4b4654016 100644 --- a/src/lib/iter-adapter.hpp +++ b/src/lib/iter-adapter.hpp @@ -448,15 +448,15 @@ namespace lib { * @see iter-explorer-test.hpp * @see iter-adaptor-test.cpp */ - template> + template> class IterStateWrapper { ST core_; public: - using value_type = meta::RefTraits::value_type; - using reference = meta::RefTraits::reference; - using pointer = meta::RefTraits::pointer; + using value_type = meta::RefTraits::value_type; + using reference = meta::RefTraits::reference; + using pointer = meta::RefTraits::pointer; IterStateWrapper (ST&& initialState) : core_(std::forward(initialState)) @@ -477,9 +477,9 @@ namespace lib { } - /* === lumiera forward iterator concept === */ + /* === Lumiera Forward Iterator concept === */ - T + RES operator*() const { __throw_if_empty(); @@ -489,13 +489,13 @@ namespace lib { pointer operator->() const { - if constexpr (meta::isLRef_v) + if constexpr (meta::isLRef_v) { __throw_if_empty(); return & core_.yield(); // core interface: yield } else - static_assert (!sizeof(T), + static_assert (!sizeof(RES), "can not provide operator-> " "since iterator pipeline generates a value"); } diff --git a/src/lib/iter-cursor.hpp b/src/lib/iter-cursor.hpp index 532ab5279..45b764691 100644 --- a/src/lib/iter-cursor.hpp +++ b/src/lib/iter-cursor.hpp @@ -56,9 +56,9 @@ namespace lib { public: - using pointer = meta::ValueTypeBinding::pointer; - using reference = meta::ValueTypeBinding::reference; - using value_type = std::remove_reference::type; ///< @note will be const for const iterators (while const_iterator::value_type isn't) + using pointer = iter::Trait::pointer; + using reference = iter::Trait::reference; + using value_type = iter::Trait::value_type; CursorGear() diff --git a/src/lib/iter-explorer.hpp b/src/lib/iter-explorer.hpp index 98b48a049..688b8ab17 100644 --- a/src/lib/iter-explorer.hpp +++ b/src/lib/iter-explorer.hpp @@ -486,6 +486,8 @@ namespace lib { + /* ========= IterExplorer pipeline building blocks ========= */ + /** * @internal Base of pipe processing decorator chain. * IterExplorer allows to create a stack out of various decorating processors @@ -517,38 +519,74 @@ namespace lib { }; + /** + * @internal Iterator adapter to _virtually project_ an arbitrary type \a RES + * onto the result of the underlying Lumiera Iterator, by means of a force-cast. + */ + template + class RedressAdapter + : public SRC + { + static_assert(can_IterForEach::value, "Lumiera Iterator required as source."); + + using SrcRes = iter::Yield; + static_assert(meta::isLRef_v, "Source iterator must yield its result by-ref."); + + public: + using value_type = meta::RefTraits::value_type; + using reference = meta::RefTraits::reference; + using pointer = meta::RefTraits::pointer; + + reference + operator*() const + { + return reinterpret_cast (SRC::operator*()); + } + + pointer + operator->() const + { + return & operator*(); + } + + using SRC::SRC; + }; + + /** * @internal Decorator for IterExplorer adding the ability to "expand children". - * The expandChildren() operation is the key element of a depth-first evaluation: it consumes - * one element and performs a preconfigured _expansion functor_ on that element to yield - * its "children". These are given in the form of another iterator, which needs to be - * compatible to the source iterator ("compatibility" boils down to both iterators - * yielding a compatible value type). Now, this _sequence of children_ effectively - * replaces the expanded source element in the overall resulting sequence; which - * means, the nested sequence was _flattened_ into the results. Since this expand() - * operation can again be invoked on the results, the implementation of such an evaluation - * requires a stack datastructure, so the nested iterator from each expand() invocation - * can be pushed to become the new active source for iteration. Thus the primary purpose - * of this Expander (decorator) is to integrate those "nested child iterators" seamlessly - * into the overall iteration process; once a child iterator is exhausted, it will be - * popped and iteration continues with the previous child iterator or finally with - * the source iterator wrapped by this decorator. The source pipeline is only pulled - * once the expanded children are exhausted. + * The expandChildren() operation is the key element of a _depth-first evaluation:_ + * it consumes one element and performs a preconfigured _expansion functor_ on that element + * to yield its "children". These are given in the form of another iterator, which needs to be + * compatible to the source iterator (within this context, "compatibility" boils down to both + * iterators yielding a compatible value type). Now, this _sequence of children_ effectively + * replaces the expanded source element in the overall resulting sequence; which means that + * the nested sequence was _flattened_ into the results. Since this expand() operation can + * again be invoked on the results, the implementation of such an evaluation requires a + * **stack datastructure**, so the nested iterator from each expand() invocation can be + * _pushed on top_ in order to become the new active source for iteration. Thus the prime + * purpose of this Expander (decorator) is to integrate those "nested child iterators" + * seamlessly into the overall iteration process; once a child iterator is exhausted, + * it will be _popped_ and iteration continues with the previous child iterator, or, + * finally, with the source iterator wrapped by this decorator. Overall, the given + * source pipeline is only pulled, whenever the expanded children are exhausted. * @remark since we allow a lot of leeway regarding the actual form and definition of the - * _expansion functor_, there is a lot of minute technical details, mostly confined - * within the _FunTraits traits. For the same reason, we need to prepare two different - * bindings of the passed raw functor, one to work on the source sequence, and the other - * one to work on the result sequence of a recursive child expansions; these two sequences - * need not be implemented in the same way, which simplifies the definition of algorithms. - * @tparam SRC the wrapped source iterator, typically a IterExplorer or nested decorator. - * @tparam FUN the concrete type of the functor passed. Will be dissected to find the signature + * _expansion functor_, various technical details must be observed, and these minutiae + * are largely maintained within the _FunTraits traits. For the same reason, we need to + * prepare two different bindings of the passed raw functor, where one of them is used + * to work on the source sequence, while the other one handles the result sequence of + * _recursive child expansions;_ these two sequences need not be implemented precisely + * in the same way — what matters here is compatible behaviour — and this flexible + * setup helps to simplify the definition of search- and evaluation algorithms. + * @tparam SRC the wrapped source iterator, typically an iterator adaptor or nested decorator. + * @tparam FUN actual type of the functor passed. Will be type-matched to detect its signature. * @note the _return type_ of #yield depends _both_ on the return type produced from the original * sequence and the return type of the sequence established through the expand functor. * An attempt is made to _reconcile these_ and this attempt may fail (at compile time). - * The reason is, any further processing downstream can not tell if data was produced - * by the original sequence of the expansion sequence. Notably, if one of these two - * delivers results by-value, then the Expander will _always_ deliver all results - * by-value, because it would not be possible to expose a reference to some value + * The rationale is that any further processing downstream can not tell if a data result + * was produced by the original sequence of the expansion sequence. Notably, if one of + * these two delivers results by-value, then the Expander will _always_ choose to deliver + * all its results by-value, since it is impossible to expose a reference to some value * that was just delivered temporarily from a source iterator. */ template @@ -727,7 +765,7 @@ namespace lib { /** - * @internal extension to the Expander decorator to perform expansion delayed on next iteration. + * @internal extension to the Expander decorator to perform a delayed expansion on next iteration. */ template class ScheduledExpander @@ -844,7 +882,7 @@ namespace lib { /** - * @internal Decorator for IterExplorer to map a transformation function on all results. + * @internal Decorator for IterExplorer to map a transformation function onto all results. * The transformation function is invoked on demand, and only once per item to be treated, * storing the treated result into an universal value holder buffer. The given functor * is adapted in a similar way as the "expand functor", so to detect and convert the @@ -871,10 +909,10 @@ namespace lib { TransformFunctor trafo_; TransformedItem treated_; - public: - using value_type = meta::ValueTypeBinding::value_type; - using reference = meta::ValueTypeBinding::reference; - using pointer = meta::ValueTypeBinding::pointer; + public: // Note: result might be a nested iterator -- not using ValueTypeBinding here! + using value_type = meta::RefTraits::value_type; + using reference = meta::RefTraits::reference; + using pointer = meta::RefTraits::pointer; template @@ -907,6 +945,7 @@ namespace lib { t1.treated_.reset(); t2.treated_.reset(); swap (t1.trafo_, t2.trafo_); + swap (t1.srcIter(), t2.srcIter()); } @@ -961,6 +1000,125 @@ namespace lib { + /** + * Iterator adapter to package each iterator result into a **wrapper object** of type \a WRA. + * Built as a simplified variant of the Transformer, it can not invoke an arbitrary function, + * yet will destroy the previous wrapper and construct a new wrapper into the embedded storage + * buffer on each iteration. + * @note similar as the Transformer, WrapperAdapter will _re-engage_ after copy, move and swap. + * This implies to discard a wrapped object residing in the ItermWrapper's storage; the next + * result-pull will then cause it to be regenerated from source, since the absolute storage + * locations both of the ItemWrapper storage, and the underlying source iterator has changed. + * Notably this protects against insidious data corruption in cases where some part of the + * implementation (typically indirectly) happens to take a reference into the source. + * @warning this mechanism might have surprising ramifications, especially when the underlying + * iterator has generated a collection by-value; this data will be discarded whenever the + * iterator pipeline is moved, which might happen automatically when adding further layers. + * Notably there is an interference with possible iterator decorators _added downstream_, + * that need to pull ahead for grouping and filtering. + * @remark Users of WrapperAdapter, and of IterExplorer are responsible in general to ensure + * that any embodied structure preferably relies on value copy of iterator results rather, + * and otherwise only employs references into stable memory locations. + */ + template + class WrapperAdapter + : public SRC + { + using WrappedItem = wrapper::ItemWrapper; + + WrappedItem wrapped_{/*created empty*/}; + + public: // Note: result might be a nested iterator -- not using ValueTypeBinding here! + using value_type = meta::RefTraits::value_type; + using reference = meta::RefTraits::reference; + using pointer = meta::RefTraits::pointer; + + WrapperAdapter (SRC&& dataSrc) + : SRC{move (dataSrc)} + { } + + WrapperAdapter() =default; + WrapperAdapter (WrapperAdapter const& o) + : SRC{o} + , wrapped_{/* deliberately empty: force re-engage */} + { } + WrapperAdapter (WrapperAdapter && o) + : SRC{move (o)} + , wrapped_{/* deliberately empty: force re-engage */} + { } + WrapperAdapter& + operator= (WrapperAdapter changed) + { + swap (*this,changed); + return *this; + } + friend void + swap (WrapperAdapter& t1, WrapperAdapter& t2) + { + using std::swap; + t1.wrapped_.reset(); + t2.wrapped_.reset(); + swap (t1.srcIter(), t2.srcIter()); + } + + + /* === decorated Lumiera Forward Iterator === */ + + reference + operator*() const + { + return unConst(this)->reWrapIt(); + } + + pointer + operator->() const + { + return & operator*(); + } + + WrapperAdapter& + operator++() + { + wrapped_.reset(); // discard current wrapper + ++ srcIter(); + return *this; + } + + /** refresh state when other layers manipulate the source sequence */ + void + expandChildren() + { + wrapped_.reset(); + SRC::expandChildren(); + } + + + ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (WrapperAdapter); + + friend bool + operator== (WrapperAdapter const& w1, WrapperAdapter const& w2) + { + return w1.srcIter() == w2.srcIter(); + } + + private: + SRC& + srcIter() const + { + return unConst(*this); + } + + reference + reWrapIt () + { + if (not wrapped_) // build a wrapper instance once per src item + wrapped_ = * srcIter(); + return *wrapped_; + } + }; + + + /** * @internal Decorator for IterExplorer to group consecutive elements into fixed sized chunks. * One group of elements is always prepared eagerly, and then the next one on iteration. @@ -1083,14 +1241,15 @@ namespace lib { /** * @internal Decorator for IterExplorer to group consecutive elements controlled by some - * grouping value \a GRP and compute an aggregate value \a AGG for each such group as + * **grouping value** \a GRP and compute an aggregate value \a AGG for each such group as * iterator yield. The first group is consumed eagerly, each further group on iteration; * thus when the aggregate for the last group appears as result, the source iterator has - * already been exhausted. The aggregate is default-initialised at start of each group - * and then the computation functor \a FAGG is invoked for each consecutive element marked - * with the same _grouping value_ — and this grouping value itself is obtained by invoking - * the functor \a FGRP on each source value. All computation are performed on-the-fly. No - * capturing or reordering of the source elements takes place, rather groups are formed + * already been exhausted. The aggregate is default-initialised at start of each group, + * followed by invoking the [computation functor](\ref #Aggregator) for each consecutive + * element that has been marked with the same _grouping value_ — and this grouping value + * itself is obtained by invoking the [grouping functor](\ref #Grouping) on each value + * produced by the underlying source iterator. All computation is performed on-the-fly. + * No capturing or reordering of the source elements takes place, rather groups are formed * based on the changes of the grouping value over the source iterator's result sequence. * @tparam AGG data type to collect the aggregate; must be default constructible and assignable * @tparam GRP value type to indicate a group @@ -1099,7 +1258,7 @@ namespace lib { * This limitation was deemed acceptable (adapting a function with several arguments would * require quite some nasty technicalities). The first argument of this `aggFun` refers * to the accumulator by value, and thereby also implicitly defines the aggregate result type. - * @warning the Aggregator \a AGG *must not capture references* to upstream internal state, because + * @warning the #Aggregator *must not capture references* to upstream internal state, because * the overall pipeline will be moved into final location _after the initial ctor call._ */ template @@ -1189,7 +1348,7 @@ namespace lib { * Similar to the Transformer, the given functor is adapted as appropriate. However, * we require the functor's result type to be convertible to bool, to serve as approval test. * The filter predicate and thus the source iterator is evaluated _eagerly_, to establish the - * *invariant* of this class: _if a "current element" exists, it has already been approved._ + * **invariant** of this class: _if a "current element" exists, it has already been approved._ */ template class Filter @@ -1282,7 +1441,7 @@ namespace lib { * capturing it _by value._ After building, this remoulded version can be assigned to the * original filter functor, under the assumption that both are roughly compatible. Moreover, * since we wrap the actual lambda into an adapter, allowing for generic lambdas to be used - * as filter predicates, this setup allows for a lot of leeway regarding the concrete predicates. + * as filter predicates, this setup provides a lot of wiggle room for defining predicates. * @note whenever the filter is remoulded, the invariant is immediately * [re-established](\ref Filter::pullFilter() ), possibly forwarding the sequence * to the next element approved by the new version of the filter. diff --git a/src/lib/itertools.hpp b/src/lib/itertools.hpp index b4eb78320..7cd61baa6 100644 --- a/src/lib/itertools.hpp +++ b/src/lib/itertools.hpp @@ -739,9 +739,9 @@ namespace lib { return bool(source_); } - using pointer = ValueTypeBinding::pointer; - using reference = ValueTypeBinding::reference; - using value_type = ValueTypeBinding::value_type; + using pointer = meta::RefTraits::pointer; + using reference = meta::RefTraits::reference; + using value_type = meta::RefTraits::value_type; }; diff --git a/src/lib/meta/tuple-accessor.hpp b/src/lib/meta/tuple-accessor.hpp new file mode 100644 index 000000000..d6eeb29bc --- /dev/null +++ b/src/lib/meta/tuple-accessor.hpp @@ -0,0 +1,611 @@ +/* + TUPLE-ACCESSOR.hpp - metaprogramming utilities for type and data tuples + + Copyright (C) + 2016 Hermann Vosseler + +  **Lumiera** is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published by the +  Free Software Foundation; either version 2 of the License, or (at your +  option) any later version. See the file COPYING for further details. + +*/ + + +/** @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. + ** + ** 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`. + ** + ** 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 + ** + */ + + +#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; +} + + +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)}; + } + + + + + + + + + /** + * 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_ACCESSOR_H*/ diff --git a/src/lib/meta/tuple-concept.hpp b/src/lib/meta/tuple-concept.hpp new file mode 100644 index 000000000..39c2568b4 --- /dev/null +++ b/src/lib/meta/tuple-concept.hpp @@ -0,0 +1,607 @@ +/* + TUPLE-CONCEPT.hpp - metaprogramming support for tuple-like data types + + Copyright (C) + 2025, Hermann Vosseler + +  **Lumiera** is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published by the +  Free Software Foundation; either version 2 of the License, or (at your +  option) any later version. See the file COPYING for further details. + +*/ + + +/** @file tuple-concept.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. + ** + ** 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 + ** 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 + ** 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. + ** @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 + ** to redefine and replace also the crucial function `std::apply`, so that a generic + ** function can be applied to any _tuple-like_ entity. + ** @see TupleHelper_test + ** @see tuple-helper.hpp extended tuple support functions + ** + */ + + +#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 +#include +#include + + +namespace util { // forward declaration + + template + std::string + toString (TY const& val) noexcept; +} + + +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)}; + } + + + + + + + + + /** + * 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 44d89fc5c..50c4d738c 100644 --- a/src/lib/meta/tuple-helper.hpp +++ b/src/lib/meta/tuple-helper.hpp @@ -2,7 +2,7 @@ TUPLE-HELPER.hpp - metaprogramming utilities for type and data tuples Copyright (C) - 2016, Hermann Vosseler + 2016,2025 Hermann Vosseler   **Lumiera** is free software; you can redistribute it and/or modify it   under the terms of the GNU General Public License as published by the @@ -18,11 +18,16 @@ ** to the tuple type provided by the standard library, including traits and ** helpers to build tuple types from metaprogramming and to pretty-print tuples. ** - ** Notably, a `concept tuple_like` is provided, which 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. - ** @note Due to an unfortunate limitation of the standard, we're forced to provide our own alternative - ** implementation to replace `std::apply`, so that a function can be applied to any _tuple-like_ + ** 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`. ** ** Furthermore, a generic iteration construct is provided, to instantiate ** a generic Lambda for each element of a given tuple, which allows to write diff --git a/src/lib/meta/value-type-binding.hpp b/src/lib/meta/value-type-binding.hpp index a1a4da703..765d62621 100644 --- a/src/lib/meta/value-type-binding.hpp +++ b/src/lib/meta/value-type-binding.hpp @@ -37,6 +37,12 @@ ** creates an *Extension Point*: when some payload type requires special ** treatment, an explicit specialisation to this rebinding trait may be ** injected alongside with the definition of the payload type. + ** @warning A special twist may happen when processing a function in an iterator (pipeline). + ** In this special case, `ValueTypeBinding` should _not_ be used, since the function + ** might yield again a nested iterator; yet we want to treat that iterator as a value + ** in that special case, and not look into the iterator or retrieve results from it. + ** Notably this situation applies to any kind of transforming iterator. + ** lib::meta::RefTraits should be used in such cases. ** ** The CommonResultYield type rebinding helper allows to reconcile several ** essentially compatible result types; it is used in iterator pipelines,