Library: prepare two wrapper-helpers to cover corner cases
Since the new `flatten()` operation was implemented in the `IterExplorer` framework, some non-obvious corner cases must be considered; notably the source iterator might yield something that is not by itself a Lumiera Iterator, yet could be adapted by applying those generic mechanisms already in place to handle such cases; however there is the special twist in the case at hand that we do not have to adapt the ''source iterator'', yet rather the ''product'' of the source iterator — a requirement that, once understood, can be easily adopted with yet another iterator-decorator layered below.
This commit is contained in:
parent
00098dd815
commit
d3407094be
8 changed files with 1449 additions and 61 deletions
|
|
@ -448,15 +448,15 @@ namespace lib {
|
||||||
* @see iter-explorer-test.hpp
|
* @see iter-explorer-test.hpp
|
||||||
* @see iter-adaptor-test.cpp
|
* @see iter-adaptor-test.cpp
|
||||||
*/
|
*/
|
||||||
template<class ST, typename T =iter::CoreYield<ST>>
|
template<class ST, typename RES =iter::CoreYield<ST>>
|
||||||
class IterStateWrapper
|
class IterStateWrapper
|
||||||
{
|
{
|
||||||
ST core_;
|
ST core_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using value_type = meta::RefTraits<T>::value_type;
|
using value_type = meta::RefTraits<RES>::value_type;
|
||||||
using reference = meta::RefTraits<T>::reference;
|
using reference = meta::RefTraits<RES>::reference;
|
||||||
using pointer = meta::RefTraits<T>::pointer;
|
using pointer = meta::RefTraits<RES>::pointer;
|
||||||
|
|
||||||
IterStateWrapper (ST&& initialState)
|
IterStateWrapper (ST&& initialState)
|
||||||
: core_(std::forward<ST>(initialState))
|
: core_(std::forward<ST>(initialState))
|
||||||
|
|
@ -477,9 +477,9 @@ namespace lib {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* === lumiera forward iterator concept === */
|
/* === Lumiera Forward Iterator concept === */
|
||||||
|
|
||||||
T
|
RES
|
||||||
operator*() const
|
operator*() const
|
||||||
{
|
{
|
||||||
__throw_if_empty();
|
__throw_if_empty();
|
||||||
|
|
@ -489,13 +489,13 @@ namespace lib {
|
||||||
pointer
|
pointer
|
||||||
operator->() const
|
operator->() const
|
||||||
{
|
{
|
||||||
if constexpr (meta::isLRef_v<T>)
|
if constexpr (meta::isLRef_v<RES>)
|
||||||
{
|
{
|
||||||
__throw_if_empty();
|
__throw_if_empty();
|
||||||
return & core_.yield(); // core interface: yield
|
return & core_.yield(); // core interface: yield
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
static_assert (!sizeof(T),
|
static_assert (!sizeof(RES),
|
||||||
"can not provide operator-> "
|
"can not provide operator-> "
|
||||||
"since iterator pipeline generates a value");
|
"since iterator pipeline generates a value");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,9 @@ namespace lib {
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using pointer = meta::ValueTypeBinding<IT>::pointer;
|
using pointer = iter::Trait<IT>::pointer;
|
||||||
using reference = meta::ValueTypeBinding<IT>::reference;
|
using reference = iter::Trait<IT>::reference;
|
||||||
using value_type = std::remove_reference<reference>::type; ///< @note will be const for const iterators (while const_iterator::value_type isn't)
|
using value_type = iter::Trait<IT>::value_type;
|
||||||
|
|
||||||
|
|
||||||
CursorGear()
|
CursorGear()
|
||||||
|
|
|
||||||
|
|
@ -486,6 +486,8 @@ namespace lib {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ========= IterExplorer pipeline building blocks ========= */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal Base of pipe processing decorator chain.
|
* @internal Base of pipe processing decorator chain.
|
||||||
* IterExplorer allows to create a stack out of various decorating processors
|
* 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 SRC, typename RES>
|
||||||
|
class RedressAdapter
|
||||||
|
: public SRC
|
||||||
|
{
|
||||||
|
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source.");
|
||||||
|
|
||||||
|
using SrcRes = iter::Yield<SRC>;
|
||||||
|
static_assert(meta::isLRef_v<SrcRes>, "Source iterator must yield its result by-ref.");
|
||||||
|
|
||||||
|
public:
|
||||||
|
using value_type = meta::RefTraits<RES>::value_type;
|
||||||
|
using reference = meta::RefTraits<RES>::reference;
|
||||||
|
using pointer = meta::RefTraits<RES>::pointer;
|
||||||
|
|
||||||
|
reference
|
||||||
|
operator*() const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<reference> (SRC::operator*());
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer
|
||||||
|
operator->() const
|
||||||
|
{
|
||||||
|
return & operator*();
|
||||||
|
}
|
||||||
|
|
||||||
|
using SRC::SRC;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal Decorator for IterExplorer adding the ability to "expand children".
|
* @internal Decorator for IterExplorer adding the ability to "expand children".
|
||||||
* The expandChildren() operation is the key element of a depth-first evaluation: it consumes
|
* The expandChildren() operation is the key element of a _depth-first evaluation:_
|
||||||
* one element and performs a preconfigured _expansion functor_ on that element to yield
|
* it consumes one element and performs a preconfigured _expansion functor_ on that element
|
||||||
* its "children". These are given in the form of another iterator, which needs to be
|
* 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
|
* compatible to the source iterator (within this context, "compatibility" boils down to both
|
||||||
* yielding a compatible value type). Now, this _sequence of children_ effectively
|
* iterators yielding a compatible value type). Now, this _sequence of children_ effectively
|
||||||
* replaces the expanded source element in the overall resulting sequence; which
|
* replaces the expanded source element in the overall resulting sequence; which means that
|
||||||
* means, the nested sequence was _flattened_ into the results. Since this expand()
|
* the nested sequence was _flattened_ into the results. Since this expand() operation can
|
||||||
* operation can again be invoked on the results, the implementation of such an evaluation
|
* again be invoked on the results, the implementation of such an evaluation requires a
|
||||||
* requires a stack datastructure, so the nested iterator from each expand() invocation
|
* **stack datastructure**, so the nested iterator from each expand() invocation can be
|
||||||
* can be pushed to become the new active source for iteration. Thus the primary purpose
|
* _pushed on top_ in order to become the new active source for iteration. Thus the prime
|
||||||
* of this Expander (decorator) is to integrate those "nested child iterators" seamlessly
|
* purpose of this Expander (decorator) is to integrate those "nested child iterators"
|
||||||
* into the overall iteration process; once a child iterator is exhausted, it will be
|
* seamlessly into the overall iteration process; once a child iterator is exhausted,
|
||||||
* popped and iteration continues with the previous child iterator or finally with
|
* it will be _popped_ and iteration continues with the previous child iterator, or,
|
||||||
* the source iterator wrapped by this decorator. The source pipeline is only pulled
|
* finally, with the source iterator wrapped by this decorator. Overall, the given
|
||||||
* once the expanded children are exhausted.
|
* 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
|
* @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
|
* _expansion functor_, various technical details must be observed, and these minutiae
|
||||||
* within the _FunTraits traits. For the same reason, we need to prepare two different
|
* are largely maintained within the _FunTraits traits. For the same reason, we need to
|
||||||
* bindings of the passed raw functor, one to work on the source sequence, and the other
|
* prepare two different bindings of the passed raw functor, where one of them is used
|
||||||
* one to work on the result sequence of a recursive child expansions; these two sequences
|
* to work on the source sequence, while the other one handles the result sequence of
|
||||||
* need not be implemented in the same way, which simplifies the definition of algorithms.
|
* _recursive child expansions;_ these two sequences need not be implemented precisely
|
||||||
* @tparam SRC the wrapped source iterator, typically a IterExplorer or nested decorator.
|
* in the same way — what matters here is compatible behaviour — and this flexible
|
||||||
* @tparam FUN the concrete type of the functor passed. Will be dissected to find the signature
|
* 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
|
* @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.
|
* 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).
|
* 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
|
* The rationale is that any further processing downstream can not tell if a data result
|
||||||
* by the original sequence of the expansion sequence. Notably, if one of these two
|
* was produced by the original sequence of the expansion sequence. Notably, if one of
|
||||||
* delivers results by-value, then the Expander will _always_ deliver all results
|
* these two delivers results by-value, then the Expander will _always_ choose to deliver
|
||||||
* by-value, because it would not be possible to expose a reference to some value
|
* 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.
|
* that was just delivered temporarily from a source iterator.
|
||||||
*/
|
*/
|
||||||
template<class SRC, class RES>
|
template<class SRC, class RES>
|
||||||
|
|
@ -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 SRC>
|
template<class SRC>
|
||||||
class ScheduledExpander
|
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,
|
* 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
|
* 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
|
* is adapted in a similar way as the "expand functor", so to detect and convert the
|
||||||
|
|
@ -871,10 +909,10 @@ namespace lib {
|
||||||
TransformFunctor trafo_;
|
TransformFunctor trafo_;
|
||||||
TransformedItem treated_;
|
TransformedItem treated_;
|
||||||
|
|
||||||
public:
|
public: // Note: result might be a nested iterator -- not using ValueTypeBinding here!
|
||||||
using value_type = meta::ValueTypeBinding<RES>::value_type;
|
using value_type = meta::RefTraits<RES>::value_type;
|
||||||
using reference = meta::ValueTypeBinding<RES>::reference;
|
using reference = meta::RefTraits<RES>::reference;
|
||||||
using pointer = meta::ValueTypeBinding<RES>::pointer;
|
using pointer = meta::RefTraits<RES>::pointer;
|
||||||
|
|
||||||
|
|
||||||
template<typename FUN>
|
template<typename FUN>
|
||||||
|
|
@ -907,6 +945,7 @@ namespace lib {
|
||||||
t1.treated_.reset();
|
t1.treated_.reset();
|
||||||
t2.treated_.reset();
|
t2.treated_.reset();
|
||||||
swap (t1.trafo_, t2.trafo_);
|
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 SRC, typename WRA>
|
||||||
|
class WrapperAdapter
|
||||||
|
: public SRC
|
||||||
|
{
|
||||||
|
using WrappedItem = wrapper::ItemWrapper<WRA>;
|
||||||
|
|
||||||
|
WrappedItem wrapped_{/*created empty*/};
|
||||||
|
|
||||||
|
public: // Note: result might be a nested iterator -- not using ValueTypeBinding here!
|
||||||
|
using value_type = meta::RefTraits<WRA>::value_type;
|
||||||
|
using reference = meta::RefTraits<WRA>::reference;
|
||||||
|
using pointer = meta::RefTraits<WRA>::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.
|
* @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.
|
* 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
|
* @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;
|
* 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
|
* 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
|
* 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
|
* followed by invoking the [computation functor](\ref #Aggregator) for each consecutive
|
||||||
* with the same _grouping value_ — and this grouping value itself is obtained by invoking
|
* element that has been marked with the same _grouping value_ — and this grouping value
|
||||||
* the functor \a FGRP on each source value. All computation are performed on-the-fly. No
|
* itself is obtained by invoking the [grouping functor](\ref #Grouping) on each value
|
||||||
* capturing or reordering of the source elements takes place, rather groups are formed
|
* 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.
|
* 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 AGG data type to collect the aggregate; must be default constructible and assignable
|
||||||
* @tparam GRP value type to indicate a group
|
* @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
|
* This limitation was deemed acceptable (adapting a function with several arguments would
|
||||||
* require quite some nasty technicalities). The first argument of this `aggFun` refers
|
* 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.
|
* 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._
|
* the overall pipeline will be moved into final location _after the initial ctor call._
|
||||||
*/
|
*/
|
||||||
template<class SRC, typename AGG, class GRP>
|
template<class SRC, typename AGG, class GRP>
|
||||||
|
|
@ -1189,7 +1348,7 @@ namespace lib {
|
||||||
* Similar to the Transformer, the given functor is adapted as appropriate. However,
|
* 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.
|
* 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
|
* 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 SRC>
|
template<class SRC>
|
||||||
class Filter
|
class Filter
|
||||||
|
|
@ -1282,7 +1441,7 @@ namespace lib {
|
||||||
* capturing it _by value._ After building, this remoulded version can be assigned to the
|
* 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,
|
* 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
|
* 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
|
* @note whenever the filter is remoulded, the invariant is immediately
|
||||||
* [re-established](\ref Filter::pullFilter() ), possibly forwarding the sequence
|
* [re-established](\ref Filter::pullFilter() ), possibly forwarding the sequence
|
||||||
* to the next element approved by the new version of the filter.
|
* to the next element approved by the new version of the filter.
|
||||||
|
|
|
||||||
|
|
@ -739,9 +739,9 @@ namespace lib {
|
||||||
return bool(source_);
|
return bool(source_);
|
||||||
}
|
}
|
||||||
|
|
||||||
using pointer = ValueTypeBinding<VAL>::pointer;
|
using pointer = meta::RefTraits<VAL>::pointer;
|
||||||
using reference = ValueTypeBinding<VAL>::reference;
|
using reference = meta::RefTraits<VAL>::reference;
|
||||||
using value_type = ValueTypeBinding<VAL>::value_type;
|
using value_type = meta::RefTraits<VAL>::value_type;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
611
src/lib/meta/tuple-accessor.hpp
Normal file
611
src/lib/meta/tuple-accessor.hpp
Normal file
|
|
@ -0,0 +1,611 @@
|
||||||
|
/*
|
||||||
|
TUPLE-ACCESSOR.hpp - metaprogramming utilities for type and data tuples
|
||||||
|
|
||||||
|
Copyright (C)
|
||||||
|
2016 Hermann Vosseler <Ichthyostega@web.de>
|
||||||
|
|
||||||
|
**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 <tuple>
|
||||||
|
#include <utility>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
|
||||||
|
namespace util { // forward declaration
|
||||||
|
|
||||||
|
template<typename TY>
|
||||||
|
std::string
|
||||||
|
toString (TY const& val) noexcept;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
namespace lib {
|
||||||
|
namespace meta {
|
||||||
|
|
||||||
|
/** trait to detect tuple types */
|
||||||
|
template<typename T>
|
||||||
|
struct is_Tuple
|
||||||
|
: std::false_type
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template<typename...TYPES>
|
||||||
|
struct is_Tuple<std::tuple<TYPES...>>
|
||||||
|
: std::true_type
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template<typename...TYPES>
|
||||||
|
struct is_Tuple<const std::tuple<TYPES...>>
|
||||||
|
: 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<class TUP>
|
||||||
|
concept tuple_sized = requires
|
||||||
|
{
|
||||||
|
{ std::tuple_size<TUP>::value } -> std::convertible_to<size_t>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** @internal building-block: a type where elements can be accessed through a `get` friend function */
|
||||||
|
template<class TUP, std::size_t idx>
|
||||||
|
concept tuple_adl_accessible = requires(TUP tup)
|
||||||
|
{
|
||||||
|
typename std::tuple_element_t<idx, TUP>;
|
||||||
|
{ get<idx>(tup) } -> std::convertible_to<std::tuple_element_t<idx, TUP>&>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @internal building-block: a type where elements can be accessed through a `get` member function */
|
||||||
|
template<class TUP, std::size_t idx>
|
||||||
|
concept tuple_mem_accessible = requires(TUP tup)
|
||||||
|
{
|
||||||
|
typename std::tuple_element_t<idx, TUP>;
|
||||||
|
{ tup.template get<idx>() } -> std::convertible_to<std::tuple_element_t<idx, TUP>&>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class TUP, std::size_t idx>
|
||||||
|
concept tuple_element_accessible = tuple_mem_accessible<TUP,idx> or tuple_adl_accessible<TUP,idx>;
|
||||||
|
|
||||||
|
template<class TUP>
|
||||||
|
concept tuple_accessible =
|
||||||
|
tuple_sized<TUP> and
|
||||||
|
WithIdxSeq<std::tuple_size_v<TUP>>::andAll([](auto idx)
|
||||||
|
{
|
||||||
|
return tuple_element_accessible<TUP,idx>;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concept to mark any type compliant to the »tuple protocol«
|
||||||
|
*/
|
||||||
|
template<class TUP>
|
||||||
|
concept tuple_like = not is_reference_v<TUP>
|
||||||
|
and tuple_sized<remove_cv_t<TUP>>
|
||||||
|
and tuple_accessible<remove_cv_t<TUP>>;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for abstracted / unified access to member elements of any _tuple-like_
|
||||||
|
* @remark preferably uses a `get<i>` member function, falling back to a
|
||||||
|
* free function `get`, which is found by ADL.
|
||||||
|
*/
|
||||||
|
template<std::size_t idx, class TUP>
|
||||||
|
requires(tuple_like<std::remove_reference_t<TUP>>)
|
||||||
|
decltype(auto)
|
||||||
|
getElm (TUP&& tup)
|
||||||
|
{
|
||||||
|
using Tup = std::remove_reference_t<TUP>;
|
||||||
|
static_assert (0 < std::tuple_size_v<Tup>);
|
||||||
|
if constexpr (tuple_mem_accessible<Tup,0>)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_reference_v<TUP>)
|
||||||
|
return tup.template get<idx>();
|
||||||
|
else
|
||||||
|
{ // return value copy when tuple given as RValue
|
||||||
|
using Elm = std::tuple_element_t<idx, TUP>;
|
||||||
|
Elm elm(tup.template get<idx>());
|
||||||
|
return elm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // ▽▽▽ ADL
|
||||||
|
using std::get;
|
||||||
|
return get<idx> (std::forward<TUP> (tup));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace { // apply to tuple-like : helpers...
|
||||||
|
|
||||||
|
/** @internal invocation helper similar to C++17 — but preferring a custom `get` impl */
|
||||||
|
template<typename FUN, typename TUP, size_t...Idx>
|
||||||
|
constexpr decltype(auto)
|
||||||
|
__unpack_and_apply (FUN&& f, TUP&& tup, std::index_sequence<Idx...>)
|
||||||
|
{
|
||||||
|
return std::invoke (std::forward<FUN> (f)
|
||||||
|
,getElm<Idx> (std::forward<TUP>(tup))...
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** @internal invoke a metafunction with \a FUN and all element types from the _tuple-like_ \a TUP */
|
||||||
|
template<template<typename...> class META, class FUN, class TUP>
|
||||||
|
struct _InvokeMetafunTup
|
||||||
|
{
|
||||||
|
using Tupl = std::decay_t<TUP>;
|
||||||
|
using Elms = ElmTypes<Tupl>::Seq;
|
||||||
|
using Args = Prepend<FUN, Elms>::Seq;
|
||||||
|
using Type = RebindVariadic<META, Args>::Type;
|
||||||
|
};
|
||||||
|
template<template<typename...> class META, class FUN, class TUP>
|
||||||
|
struct _InvokeMetafunTup<META, FUN, TUP&>
|
||||||
|
{
|
||||||
|
using Tupl = std::decay_t<TUP>;
|
||||||
|
using Elms = typename ElmTypes<Tupl>::template Apply<std::add_lvalue_reference_t>;
|
||||||
|
using Args = Prepend<FUN, Elms>::Seq;
|
||||||
|
using Type = RebindVariadic<META, Args>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class FUN, class TUP>
|
||||||
|
inline constexpr bool can_nothrow_invoke_tup = _InvokeMetafunTup<std::is_nothrow_invocable,FUN,TUP>::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<class FUN, class TUP> requires(tuple_like<remove_reference_t<TUP>>)
|
||||||
|
constexpr decltype(auto)
|
||||||
|
apply (FUN&& f, TUP&& tup) noexcept (can_nothrow_invoke_tup<FUN,TUP> )
|
||||||
|
{
|
||||||
|
using Indices = std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<TUP>>>;
|
||||||
|
|
||||||
|
return __unpack_and_apply (std::forward<FUN> (f)
|
||||||
|
,std::forward<TUP> (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<class TUP, class FUN> requires(tuple_like<remove_reference_t<TUP>>)
|
||||||
|
constexpr void
|
||||||
|
forEach (TUP&& tuple, FUN fun)
|
||||||
|
{
|
||||||
|
lib::meta::apply ([&fun]<typename...ELMS>(ELMS&&... elms)
|
||||||
|
{
|
||||||
|
(fun (std::forward<ELMS>(elms)), ...);
|
||||||
|
}
|
||||||
|
,std::forward<TUP> (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<class TUP, class FUN> requires(tuple_like<remove_reference_t<TUP>>)
|
||||||
|
constexpr auto
|
||||||
|
mapEach (TUP&& tuple, FUN fun)
|
||||||
|
{
|
||||||
|
return lib::meta::apply ([&fun]<typename...ELMS>(ELMS&&... elms)
|
||||||
|
{ //..construct the type explicitly (make_tuple would decay fun result types)
|
||||||
|
using Tuple = std::tuple<decltype(fun (std::forward<ELMS>(elms))) ...>;
|
||||||
|
return Tuple (fun (std::forward<ELMS>(elms)) ...);
|
||||||
|
}
|
||||||
|
,std::forward<TUP> (tuple));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialisation of variadic access for any tuple-like
|
||||||
|
* @see variadic-helper.hpp
|
||||||
|
*/
|
||||||
|
template<tuple_like TUP>
|
||||||
|
struct ElmTypes<TUP>
|
||||||
|
{
|
||||||
|
template<typename>
|
||||||
|
struct Extract;
|
||||||
|
template<size_t...idx>
|
||||||
|
struct Extract<std::index_sequence<idx...>>
|
||||||
|
{
|
||||||
|
using ElmTypes = Types<std::tuple_element_t<idx,TUP> ...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr size_t SIZ = std::tuple_size_v<TUP>;
|
||||||
|
|
||||||
|
using Idx = std::make_index_sequence<SIZ>;
|
||||||
|
using Seq = Extract<Idx>::ElmTypes;
|
||||||
|
using Tup = RebindVariadic<std::tuple, Seq>::Type;
|
||||||
|
|
||||||
|
template<template<class> class META>
|
||||||
|
using Apply = ElmTypes<Seq>::template Apply<META>;
|
||||||
|
|
||||||
|
template<template<typename...> class O>
|
||||||
|
using Rebind = RebindVariadic<O, Seq>::Type;
|
||||||
|
|
||||||
|
template<template<class> class PRED>
|
||||||
|
using AndAll = ElmTypes<Apply<PRED>>::template Rebind<std::__and_>;
|
||||||
|
|
||||||
|
template<template<class> class PRED>
|
||||||
|
using OrAll = ElmTypes<Apply<PRED>>::template Rebind<std::__or_>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace { // rebinding helper to create std::tuple from a type sequence
|
||||||
|
|
||||||
|
template<typename SEQ>
|
||||||
|
struct BuildTupleType
|
||||||
|
: std::false_type
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template<typename...TYPES>
|
||||||
|
struct BuildTupleType<Types<TYPES...>>
|
||||||
|
{
|
||||||
|
using Type = std::tuple<TYPES...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class H, typename TAIL>
|
||||||
|
struct BuildTupleType<Node<H, TAIL>>
|
||||||
|
{
|
||||||
|
using Seq = Types<Node<H,TAIL>>::Seq;
|
||||||
|
using Type = BuildTupleType<Seq>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct BuildTupleType<Nil>
|
||||||
|
{
|
||||||
|
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<typename TYPES>
|
||||||
|
using Tuple = BuildTupleType<TYPES>::Type;
|
||||||
|
|
||||||
|
|
||||||
|
using std::tuple_size;
|
||||||
|
using std::tuple_element;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** match and rebind the type sequence from a tuple */
|
||||||
|
template<typename...TYPES>
|
||||||
|
struct RebindTupleTypes
|
||||||
|
{
|
||||||
|
using Seq = Types<TYPES...>::Seq;
|
||||||
|
using List = Seq::List;
|
||||||
|
};
|
||||||
|
template<typename...TYPES>
|
||||||
|
struct RebindTupleTypes<std::tuple<TYPES...>>
|
||||||
|
{
|
||||||
|
using Seq = Types<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<TYPES>`
|
||||||
|
* - 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,class, size_t> class _ElmMapper_
|
||||||
|
>
|
||||||
|
struct TupleConstructor
|
||||||
|
: Tuple<TYPES>
|
||||||
|
{
|
||||||
|
/** meta-sequence to drive instantiation of the ElmMapper */
|
||||||
|
using SequenceIterator = BuildIdxIter<TYPES>::Ascending;
|
||||||
|
|
||||||
|
template<size_t idx, class SRC>
|
||||||
|
static auto
|
||||||
|
mapElm (SRC&& init) ///< initialise an instance of the element-mapper
|
||||||
|
{
|
||||||
|
return _ElmMapper_<std::decay_t<SRC>
|
||||||
|
,Tuple<TYPES>
|
||||||
|
, idx
|
||||||
|
>{std::forward<SRC> (init)};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<class SRC, size_t...idx>
|
||||||
|
TupleConstructor (SRC&& initVals, IndexSeq<idx...>)
|
||||||
|
: Tuple<TYPES> {mapElm<idx> (std::forward<SRC>(initVals)) ...}
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
template<class SRC>
|
||||||
|
TupleConstructor (SRC&& values)
|
||||||
|
: TupleConstructor (std::forward<SRC>(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<size_t>`,
|
||||||
|
* which in turn picks and converts the value for the n-th
|
||||||
|
* tuple element.
|
||||||
|
*/
|
||||||
|
template<class SRC, class TAR>
|
||||||
|
struct ElementExtractor;
|
||||||
|
|
||||||
|
|
||||||
|
template<class SRC, class TAR, size_t i>
|
||||||
|
using ExtractArg = ElementExtractor<SRC, TAR>::template Access<i>;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<GenNode>`, 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<typename TYPES, class SRC>
|
||||||
|
Tuple<TYPES>
|
||||||
|
buildTuple (SRC&& values)
|
||||||
|
{
|
||||||
|
return TupleConstructor<TYPES, ExtractArg>{std::forward<SRC> (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<TYPES>` ). 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,class,class, uint> class _X_ ///< user provided template<Type, Base, TupleType, arg-idx>
|
||||||
|
, typename TYPES ///< Sequence of types to use within the Accessor
|
||||||
|
, class TUP =Tuple<TYPES> ///< the tuple type to build on
|
||||||
|
, uint i = 0 ///< tuple element index counter
|
||||||
|
>
|
||||||
|
class BuildTupleAccessor
|
||||||
|
{
|
||||||
|
// prepare recursion...
|
||||||
|
using Head = Split<TYPES>::Head;
|
||||||
|
using Tail = Split<TYPES>::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,class,class, uint> class _X_
|
||||||
|
, class TUP
|
||||||
|
, uint i
|
||||||
|
>
|
||||||
|
class BuildTupleAccessor< _X_, Types<>, TUP, i>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Product = _X_<Nil, TUP, TUP, i>; // 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<idx>(*this))+",");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class TUP, uint n>
|
||||||
|
struct TupleElementDisplayer<Nil, TUP, TUP, n>
|
||||||
|
: 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<typename...TYPES>
|
||||||
|
inline std::string
|
||||||
|
dump (std::tuple<TYPES...> const& tuple)
|
||||||
|
{
|
||||||
|
using BuildAccessor = BuildTupleAccessor<TupleElementDisplayer, Types<TYPES...>>;
|
||||||
|
using Displayer = BuildAccessor::Product ;
|
||||||
|
|
||||||
|
return static_cast<Displayer const&> (tuple)
|
||||||
|
.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}} // namespace lib::meta
|
||||||
|
|
||||||
|
|
||||||
|
// add a specialisation to enable tuple string conversion
|
||||||
|
namespace util {
|
||||||
|
|
||||||
|
template<typename...TYPES>
|
||||||
|
struct StringConv<std::tuple<TYPES...>>
|
||||||
|
{
|
||||||
|
static std::string
|
||||||
|
invoke (std::tuple<TYPES...> const& tuple) noexcept
|
||||||
|
try {
|
||||||
|
return "«"+typeStr(tuple)
|
||||||
|
+ "»──" + lib::meta::dump (tuple);
|
||||||
|
}
|
||||||
|
catch(...) { return FAILURE_INDICATOR; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace util
|
||||||
|
#endif /*LIB_META_TUPLE_ACCESSOR_H*/
|
||||||
607
src/lib/meta/tuple-concept.hpp
Normal file
607
src/lib/meta/tuple-concept.hpp
Normal file
|
|
@ -0,0 +1,607 @@
|
||||||
|
/*
|
||||||
|
TUPLE-CONCEPT.hpp - metaprogramming support for tuple-like data types
|
||||||
|
|
||||||
|
Copyright (C)
|
||||||
|
2025, Hermann Vosseler <Ichthyostega@web.de>
|
||||||
|
|
||||||
|
**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 <tuple>
|
||||||
|
#include <utility>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
|
||||||
|
namespace util { // forward declaration
|
||||||
|
|
||||||
|
template<typename TY>
|
||||||
|
std::string
|
||||||
|
toString (TY const& val) noexcept;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
namespace lib {
|
||||||
|
namespace meta {
|
||||||
|
|
||||||
|
/** trait to detect tuple types */
|
||||||
|
template<typename T>
|
||||||
|
struct is_Tuple
|
||||||
|
: std::false_type
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template<typename...TYPES>
|
||||||
|
struct is_Tuple<std::tuple<TYPES...>>
|
||||||
|
: std::true_type
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template<typename...TYPES>
|
||||||
|
struct is_Tuple<const std::tuple<TYPES...>>
|
||||||
|
: 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<class TUP>
|
||||||
|
concept tuple_sized = requires
|
||||||
|
{
|
||||||
|
{ std::tuple_size<TUP>::value } -> std::convertible_to<size_t>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** @internal building-block: a type where elements can be accessed through a `get` friend function */
|
||||||
|
template<class TUP, std::size_t idx>
|
||||||
|
concept tuple_adl_accessible = requires(TUP tup)
|
||||||
|
{
|
||||||
|
typename std::tuple_element_t<idx, TUP>;
|
||||||
|
{ get<idx>(tup) } -> std::convertible_to<std::tuple_element_t<idx, TUP>&>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @internal building-block: a type where elements can be accessed through a `get` member function */
|
||||||
|
template<class TUP, std::size_t idx>
|
||||||
|
concept tuple_mem_accessible = requires(TUP tup)
|
||||||
|
{
|
||||||
|
typename std::tuple_element_t<idx, TUP>;
|
||||||
|
{ tup.template get<idx>() } -> std::convertible_to<std::tuple_element_t<idx, TUP>&>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class TUP, std::size_t idx>
|
||||||
|
concept tuple_element_accessible = tuple_mem_accessible<TUP,idx> or tuple_adl_accessible<TUP,idx>;
|
||||||
|
|
||||||
|
template<class TUP>
|
||||||
|
concept tuple_accessible =
|
||||||
|
tuple_sized<TUP> and
|
||||||
|
WithIdxSeq<std::tuple_size_v<TUP>>::andAll([](auto idx)
|
||||||
|
{
|
||||||
|
return tuple_element_accessible<TUP,idx>;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concept to mark any type compliant to the »tuple protocol«
|
||||||
|
*/
|
||||||
|
template<class TUP>
|
||||||
|
concept tuple_like = not is_reference_v<TUP>
|
||||||
|
and tuple_sized<remove_cv_t<TUP>>
|
||||||
|
and tuple_accessible<remove_cv_t<TUP>>;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for abstracted / unified access to member elements of any _tuple-like_
|
||||||
|
* @remark preferably uses a `get<i>` member function, falling back to a
|
||||||
|
* free function `get`, which is found by ADL.
|
||||||
|
*/
|
||||||
|
template<std::size_t idx, class TUP>
|
||||||
|
requires(tuple_like<std::remove_reference_t<TUP>>)
|
||||||
|
decltype(auto)
|
||||||
|
getElm (TUP&& tup)
|
||||||
|
{
|
||||||
|
using Tup = std::remove_reference_t<TUP>;
|
||||||
|
static_assert (0 < std::tuple_size_v<Tup>);
|
||||||
|
if constexpr (tuple_mem_accessible<Tup,0>)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_reference_v<TUP>)
|
||||||
|
return tup.template get<idx>();
|
||||||
|
else
|
||||||
|
{ // return value copy when tuple given as RValue
|
||||||
|
using Elm = std::tuple_element_t<idx, TUP>;
|
||||||
|
Elm elm(tup.template get<idx>());
|
||||||
|
return elm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // ▽▽▽ ADL
|
||||||
|
using std::get;
|
||||||
|
return get<idx> (std::forward<TUP> (tup));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace { // apply to tuple-like : helpers...
|
||||||
|
|
||||||
|
/** @internal invocation helper similar to C++17 — but preferring a custom `get` impl */
|
||||||
|
template<typename FUN, typename TUP, size_t...Idx>
|
||||||
|
constexpr decltype(auto)
|
||||||
|
__unpack_and_apply (FUN&& f, TUP&& tup, std::index_sequence<Idx...>)
|
||||||
|
{
|
||||||
|
return std::invoke (std::forward<FUN> (f)
|
||||||
|
,getElm<Idx> (std::forward<TUP>(tup))...
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** @internal invoke a metafunction with \a FUN and all element types from the _tuple-like_ \a TUP */
|
||||||
|
template<template<typename...> class META, class FUN, class TUP>
|
||||||
|
struct _InvokeMetafunTup
|
||||||
|
{
|
||||||
|
using Tupl = std::decay_t<TUP>;
|
||||||
|
using Elms = ElmTypes<Tupl>::Seq;
|
||||||
|
using Args = Prepend<FUN, Elms>::Seq;
|
||||||
|
using Type = RebindVariadic<META, Args>::Type;
|
||||||
|
};
|
||||||
|
template<template<typename...> class META, class FUN, class TUP>
|
||||||
|
struct _InvokeMetafunTup<META, FUN, TUP&>
|
||||||
|
{
|
||||||
|
using Tupl = std::decay_t<TUP>;
|
||||||
|
using Elms = typename ElmTypes<Tupl>::template Apply<std::add_lvalue_reference_t>;
|
||||||
|
using Args = Prepend<FUN, Elms>::Seq;
|
||||||
|
using Type = RebindVariadic<META, Args>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class FUN, class TUP>
|
||||||
|
inline constexpr bool can_nothrow_invoke_tup = _InvokeMetafunTup<std::is_nothrow_invocable,FUN,TUP>::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<class FUN, class TUP> requires(tuple_like<remove_reference_t<TUP>>)
|
||||||
|
constexpr decltype(auto)
|
||||||
|
apply (FUN&& f, TUP&& tup) noexcept (can_nothrow_invoke_tup<FUN,TUP> )
|
||||||
|
{
|
||||||
|
using Indices = std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<TUP>>>;
|
||||||
|
|
||||||
|
return __unpack_and_apply (std::forward<FUN> (f)
|
||||||
|
,std::forward<TUP> (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<class TUP, class FUN> requires(tuple_like<remove_reference_t<TUP>>)
|
||||||
|
constexpr void
|
||||||
|
forEach (TUP&& tuple, FUN fun)
|
||||||
|
{
|
||||||
|
lib::meta::apply ([&fun]<typename...ELMS>(ELMS&&... elms)
|
||||||
|
{
|
||||||
|
(fun (std::forward<ELMS>(elms)), ...);
|
||||||
|
}
|
||||||
|
,std::forward<TUP> (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<class TUP, class FUN> requires(tuple_like<remove_reference_t<TUP>>)
|
||||||
|
constexpr auto
|
||||||
|
mapEach (TUP&& tuple, FUN fun)
|
||||||
|
{
|
||||||
|
return lib::meta::apply ([&fun]<typename...ELMS>(ELMS&&... elms)
|
||||||
|
{ //..construct the type explicitly (make_tuple would decay fun result types)
|
||||||
|
using Tuple = std::tuple<decltype(fun (std::forward<ELMS>(elms))) ...>;
|
||||||
|
return Tuple (fun (std::forward<ELMS>(elms)) ...);
|
||||||
|
}
|
||||||
|
,std::forward<TUP> (tuple));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialisation of variadic access for any tuple-like
|
||||||
|
* @see variadic-helper.hpp
|
||||||
|
*/
|
||||||
|
template<tuple_like TUP>
|
||||||
|
struct ElmTypes<TUP>
|
||||||
|
{
|
||||||
|
template<typename>
|
||||||
|
struct Extract;
|
||||||
|
template<size_t...idx>
|
||||||
|
struct Extract<std::index_sequence<idx...>>
|
||||||
|
{
|
||||||
|
using ElmTypes = Types<std::tuple_element_t<idx,TUP> ...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr size_t SIZ = std::tuple_size_v<TUP>;
|
||||||
|
|
||||||
|
using Idx = std::make_index_sequence<SIZ>;
|
||||||
|
using Seq = Extract<Idx>::ElmTypes;
|
||||||
|
using Tup = RebindVariadic<std::tuple, Seq>::Type;
|
||||||
|
|
||||||
|
template<template<class> class META>
|
||||||
|
using Apply = ElmTypes<Seq>::template Apply<META>;
|
||||||
|
|
||||||
|
template<template<typename...> class O>
|
||||||
|
using Rebind = RebindVariadic<O, Seq>::Type;
|
||||||
|
|
||||||
|
template<template<class> class PRED>
|
||||||
|
using AndAll = ElmTypes<Apply<PRED>>::template Rebind<std::__and_>;
|
||||||
|
|
||||||
|
template<template<class> class PRED>
|
||||||
|
using OrAll = ElmTypes<Apply<PRED>>::template Rebind<std::__or_>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace { // rebinding helper to create std::tuple from a type sequence
|
||||||
|
|
||||||
|
template<typename SEQ>
|
||||||
|
struct BuildTupleType
|
||||||
|
: std::false_type
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template<typename...TYPES>
|
||||||
|
struct BuildTupleType<Types<TYPES...>>
|
||||||
|
{
|
||||||
|
using Type = std::tuple<TYPES...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class H, typename TAIL>
|
||||||
|
struct BuildTupleType<Node<H, TAIL>>
|
||||||
|
{
|
||||||
|
using Seq = Types<Node<H,TAIL>>::Seq;
|
||||||
|
using Type = BuildTupleType<Seq>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct BuildTupleType<Nil>
|
||||||
|
{
|
||||||
|
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<typename TYPES>
|
||||||
|
using Tuple = BuildTupleType<TYPES>::Type;
|
||||||
|
|
||||||
|
|
||||||
|
using std::tuple_size;
|
||||||
|
using std::tuple_element;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** match and rebind the type sequence from a tuple */
|
||||||
|
template<typename...TYPES>
|
||||||
|
struct RebindTupleTypes
|
||||||
|
{
|
||||||
|
using Seq = Types<TYPES...>::Seq;
|
||||||
|
using List = Seq::List;
|
||||||
|
};
|
||||||
|
template<typename...TYPES>
|
||||||
|
struct RebindTupleTypes<std::tuple<TYPES...>>
|
||||||
|
{
|
||||||
|
using Seq = Types<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<TYPES>`
|
||||||
|
* - 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,class, size_t> class _ElmMapper_
|
||||||
|
>
|
||||||
|
struct TupleConstructor
|
||||||
|
: Tuple<TYPES>
|
||||||
|
{
|
||||||
|
/** meta-sequence to drive instantiation of the ElmMapper */
|
||||||
|
using SequenceIterator = BuildIdxIter<TYPES>::Ascending;
|
||||||
|
|
||||||
|
template<size_t idx, class SRC>
|
||||||
|
static auto
|
||||||
|
mapElm (SRC&& init) ///< initialise an instance of the element-mapper
|
||||||
|
{
|
||||||
|
return _ElmMapper_<std::decay_t<SRC>
|
||||||
|
,Tuple<TYPES>
|
||||||
|
, idx
|
||||||
|
>{std::forward<SRC> (init)};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<class SRC, size_t...idx>
|
||||||
|
TupleConstructor (SRC&& initVals, IndexSeq<idx...>)
|
||||||
|
: Tuple<TYPES> {mapElm<idx> (std::forward<SRC>(initVals)) ...}
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
template<class SRC>
|
||||||
|
TupleConstructor (SRC&& values)
|
||||||
|
: TupleConstructor (std::forward<SRC>(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<size_t>`,
|
||||||
|
* which in turn picks and converts the value for the n-th
|
||||||
|
* tuple element.
|
||||||
|
*/
|
||||||
|
template<class SRC, class TAR>
|
||||||
|
struct ElementExtractor;
|
||||||
|
|
||||||
|
|
||||||
|
template<class SRC, class TAR, size_t i>
|
||||||
|
using ExtractArg = ElementExtractor<SRC, TAR>::template Access<i>;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<GenNode>`, 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<typename TYPES, class SRC>
|
||||||
|
Tuple<TYPES>
|
||||||
|
buildTuple (SRC&& values)
|
||||||
|
{
|
||||||
|
return TupleConstructor<TYPES, ExtractArg>{std::forward<SRC> (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<TYPES>` ). 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,class,class, uint> class _X_ ///< user provided template<Type, Base, TupleType, arg-idx>
|
||||||
|
, typename TYPES ///< Sequence of types to use within the Accessor
|
||||||
|
, class TUP =Tuple<TYPES> ///< the tuple type to build on
|
||||||
|
, uint i = 0 ///< tuple element index counter
|
||||||
|
>
|
||||||
|
class BuildTupleAccessor
|
||||||
|
{
|
||||||
|
// prepare recursion...
|
||||||
|
using Head = Split<TYPES>::Head;
|
||||||
|
using Tail = Split<TYPES>::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,class,class, uint> class _X_
|
||||||
|
, class TUP
|
||||||
|
, uint i
|
||||||
|
>
|
||||||
|
class BuildTupleAccessor< _X_, Types<>, TUP, i>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Product = _X_<Nil, TUP, TUP, i>; // 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<idx>(*this))+",");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class TUP, uint n>
|
||||||
|
struct TupleElementDisplayer<Nil, TUP, TUP, n>
|
||||||
|
: 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<typename...TYPES>
|
||||||
|
inline std::string
|
||||||
|
dump (std::tuple<TYPES...> const& tuple)
|
||||||
|
{
|
||||||
|
using BuildAccessor = BuildTupleAccessor<TupleElementDisplayer, Types<TYPES...>>;
|
||||||
|
using Displayer = BuildAccessor::Product ;
|
||||||
|
|
||||||
|
return static_cast<Displayer const&> (tuple)
|
||||||
|
.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}} // namespace lib::meta
|
||||||
|
|
||||||
|
|
||||||
|
// add a specialisation to enable tuple string conversion
|
||||||
|
namespace util {
|
||||||
|
|
||||||
|
template<typename...TYPES>
|
||||||
|
struct StringConv<std::tuple<TYPES...>>
|
||||||
|
{
|
||||||
|
static std::string
|
||||||
|
invoke (std::tuple<TYPES...> const& tuple) noexcept
|
||||||
|
try {
|
||||||
|
return "«"+typeStr(tuple)
|
||||||
|
+ "»──" + lib::meta::dump (tuple);
|
||||||
|
}
|
||||||
|
catch(...) { return FAILURE_INDICATOR; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace util
|
||||||
|
#endif /*LIB_META_TUPLE_CONCEPT_H*/
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
TUPLE-HELPER.hpp - metaprogramming utilities for type and data tuples
|
TUPLE-HELPER.hpp - metaprogramming utilities for type and data tuples
|
||||||
|
|
||||||
Copyright (C)
|
Copyright (C)
|
||||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
2016,2025 Hermann Vosseler <Ichthyostega@web.de>
|
||||||
|
|
||||||
**Lumiera** is free software; you can redistribute it and/or modify it
|
**Lumiera** is free software; you can redistribute it and/or modify it
|
||||||
under the terms of the GNU General Public License as published by the
|
under the terms of the GNU General Public License as published by the
|
||||||
|
|
@ -18,11 +18,16 @@
|
||||||
** to the tuple type provided by the standard library, including traits and
|
** to the tuple type provided by the standard library, including traits and
|
||||||
** helpers to build tuple types from metaprogramming and to pretty-print tuples.
|
** 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
|
** Notably the handling of tuples is based on a **Concept** `tuple_like`,
|
||||||
** with the »tuple protocol«. Together with a [generic accessor][\ref lib::meta::getElm],
|
** that is satisfied for any type in compliance with the »tuple protocol«.
|
||||||
** this allows to handle all _tuple-like_ types uniformly.
|
** Together with a [generic accessor][\ref lib::meta::getElm], this allows
|
||||||
** @note Due to an unfortunate limitation of the standard, we're forced to provide our own alternative
|
** to handle all _tuple-like_ types uniformly.
|
||||||
** implementation to replace `std::apply`, so that a function can be applied to any _tuple-like_
|
** 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
|
** Furthermore, a generic iteration construct is provided, to instantiate
|
||||||
** a generic Lambda for each element of a given tuple, which allows to write
|
** a generic Lambda for each element of a given tuple, which allows to write
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,12 @@
|
||||||
** creates an *Extension Point*: when some payload type requires special
|
** creates an *Extension Point*: when some payload type requires special
|
||||||
** treatment, an explicit specialisation to this rebinding trait may be
|
** treatment, an explicit specialisation to this rebinding trait may be
|
||||||
** injected alongside with the definition of the payload type.
|
** 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
|
** The CommonResultYield type rebinding helper allows to reconcile several
|
||||||
** essentially compatible result types; it is used in iterator pipelines,
|
** essentially compatible result types; it is used in iterator pipelines,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue