LUMIERA.clone/src/lib/meta/tuple-helper.hpp
Ichthyostega 49f2e34e4c Library: extract type rebinding helper
...turns out this is entirely generic and not tied to the context
within ActivityDetector, where it was first introduced to build a
mock functor to log all invocations.

Basically this meta-function generates a new instantiation of the
template X, using the variadic argument pack from template U<ARGS...>
2023-08-01 14:52:20 +02:00

387 lines
13 KiB
C++

/*
TUPLE-HELPER.hpp - metaprogramming utilities for type and data tuples
Copyright (C) Lumiera.org
2016, Hermann Vosseler <Ichthyostega@web.de>
This program 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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file tuple-helper.hpp
** Metaprogramming with tuples-of-types and the `std::tuple` record.
** The metaprogramming part of this header complements typelist.hpp and allows
** some additional manipulations on type sequences, especially to integrate
** with the Tuples provided by the standard library.
**
** @warning the metaprogramming part of Lumiera to deal with type sequences is in a
** state of transition, since C++11 now offers direct language support for
** processing of flexible template parameter sequences ("parameter packs").
** It is planned to regroup and simplify our homemade type sequence framework
** to rely on variadic templates and integrate better with std::tuple.
** It is clear that we will _retain some parts_ of our own framework,
** since programming with _Loki-style typelists_ is way more obvious
** and straight forward than handling of template parameter packs,
** since the latter can only be rebound through pattern matching.
** @todo transition lib::meta::Types to variadic parameters /////////////////////////////////TICKET #987
**
** @see control::CommandDef usage example
** @see TupleHelper_test
** @see typelist.hpp
** @see function.hpp
** @see generator.hpp
**
*/
#ifndef LIB_META_TUPLE_HELPER_H
#define LIB_META_TUPLE_HELPER_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>
namespace util { // forward declaration
template<typename TY>
std::string
toString (TY const& val) noexcept;
}
namespace lib {
namespace meta {
namespace { // rebinding helper to create std::tuple from a type sequence
template<typename SEQ>
struct BuildTupleType
: std::false_type
{ };
template<typename...TYPES>
struct BuildTupleType<TySeq<TYPES...>>
{
using Type = std::tuple<TYPES...>;
};
/**
* temporary workaround: strip trailing NullType entries
* prior to rebinding to the `std::tuple` type.
*/
template<typename...TYPES>
struct BuildTupleType<Types<TYPES...>>
{
using VariadicSeq = typename StripNullType<Types<TYPES...>>::Seq;
using Type = typename BuildTupleType<VariadicSeq>::Type;
};
template<class H, typename TAIL>
struct BuildTupleType<Node<H, TAIL>>
{
using Seq = typename Types< Node<H,TAIL>>::Seq;
using Type = typename BuildTupleType<Seq>::Type;
};
template<>
struct BuildTupleType<NullType>
{
using Type = typename BuildTupleType<Types<>>::Type;
};
}
/** 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 = typename BuildTupleType<TYPES>::Type;
using std::tuple_size;
using std::tuple_element;
/** temporary workaround: match and rebind the type sequence from a tuple */
template<typename...TYPES>
struct RebindTupleTypes
{
using Seq = typename Types<TYPES...>::Seq;
using List = typename Seq::List;
};
template<typename...TYPES>
struct RebindTupleTypes<std::tuple<TYPES...>>
{
using Seq = typename Types<TYPES...>::Seq;
using List = typename Seq::List;
};
/** 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
{ };
/**
* 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. The sole
* requirement is that the concrete instance is _assignable_ by the source type
* and _convertible_ to the individual member type of the target tuple it is
* invoked for. Moreover, it is possible to build a generic _element extractor_,
* which will be specialised on base of the source type accepted.
* @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 = typename BuildIdxIter<TYPES>::Ascending;
protected:
template<class SRC, size_t...idx>
TupleConstructor (SRC values, IndexSeq<idx...>)
: Tuple<TYPES> (_ElmMapper_<SRC, Tuple<TYPES>, idx>{values}...)
{ }
public:
template<class SRC>
TupleConstructor (SRC values)
: TupleConstructor (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 = typename 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> (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 = typename Split<TYPES>::Head;
using Tail = typename Split<TYPES>::Tail;
using NextBuilder = BuildTupleAccessor<_X_, Tail,TUP, i+1>;
using NextAccessor = typename 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_<NullType, 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<NullType, 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 = typename 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_HELPER_H*/