Library: deep dive into type reconciliation and tuple handling
This commit is the aggregated result of extended research that is only loosely related to current development topics. One theme was the construction of nested data structures using tuples, which faces some difficulties due to tuple manipulations being limited almost entirely to compile-time processing. However, the barrier between compile-time and runtime can be overcome with the help of a dispatcher table -- which leads to the secondary problem to handle the diverse tuple elements with a common, generic functor. Such a functor can be constructed, if and only if those element types are ''reconcilable'' The STDLIB provides two metafunctions, `std::common_type` and `std::common_refernce`, which however pose some challenges for the task at hand, since they include additional processing. In the end, a custom version of the ''basic type reconciliation'' was implemented and tested, and then used as foundation to build a dispatcher table for runtime-indexed access and iteration over tuples. As an aside, several deeply hidden bugs in our library code were resolved, and the tuple-helpers, tuple string rendering and associated utilities were regrouped and modernised. As result of this research work, the following components are provided: - meta/common-result - meta/fun-trampoline - meta/tuple-index-adaptor
This commit is contained in:
parent
a835a796b2
commit
0760525ea5
30 changed files with 4071 additions and 454 deletions
403
research/try.cpp
403
research/try.cpp
|
|
@ -1,6 +1,7 @@
|
|||
/* try.cpp - to try out and experiment with new features....
|
||||
* scons will create the binary bin/try
|
||||
*/
|
||||
// 03/26 - runtime index access and iteration over tuple-likes
|
||||
// 06/25 - provide a concept to accept _tuple-like_ objects
|
||||
// 06/25 - investigate function type detection of std::bind Binders
|
||||
// 12/24 - investigate problem when perfect-forwarding into a binder
|
||||
|
|
@ -10,22 +11,28 @@
|
|||
|
||||
|
||||
/** @file try.cpp
|
||||
* Develop a concept to detect _tuple-like_ classes, based on the requirements
|
||||
* of the »tuple protocol«. Using some ideas from [Stackoverflow] as starting point.
|
||||
* However, we model both a _friend function_ `get` and a similar member function
|
||||
* as alternatives for element access, and we break down the checks into sub-concepts.
|
||||
* [Stackoverflow]: https://stackoverflow.com/q/68443804/444796
|
||||
* Work out a construction scheme to build an efficient element-accessor to data
|
||||
* in a tuple-like with run-time indexing. As an extension, run-time pulled iteration
|
||||
* can be established, using the IterExplorer framework.
|
||||
* @see lib::meta::FunTrampoline
|
||||
* @see lib::meta::TupleIdxAdaptor
|
||||
*/
|
||||
|
||||
|
||||
#include "lib/format-cout.hpp"
|
||||
#include "lib/format-util.hpp"
|
||||
#include "test/test-helper.hpp"
|
||||
#include "test/diagnostic-output.hpp"
|
||||
#include "lib/meta/common-result.hpp"
|
||||
#include "lib/meta/tuple-concept.hpp"
|
||||
#include "lib/meta/tuple-helper.hpp"
|
||||
#include "lib/meta/function.hpp"
|
||||
#include "lib/iter-adapter.hpp"
|
||||
#include "lib/hetero-data.hpp"
|
||||
#include "lib/meta/trait.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <concepts>
|
||||
#include <array>
|
||||
|
||||
using std::string;
|
||||
|
||||
|
|
@ -41,7 +48,7 @@ template<lib::meta::tuple_like X>
|
|||
void
|
||||
show()
|
||||
{
|
||||
cout << "Tup!! "<< ::test::showType<X>() <<endl;
|
||||
cout << "Tulik!! "<< ::test::showType<X>() <<endl;
|
||||
lib::meta::forEachIDX<X> ([](auto i)
|
||||
{
|
||||
using Elm = std::tuple_element_t<i, X>;
|
||||
|
|
@ -49,6 +56,255 @@ show()
|
|||
});
|
||||
}
|
||||
|
||||
namespace lib {
|
||||
namespace meta{
|
||||
|
||||
#if false ///////////////////////////////////////////////////////////////////////////////////////////////////TODO : new generic library functions
|
||||
/**
|
||||
* Concept to require a generic type sequence `Types<TYPES...>`
|
||||
*/
|
||||
template<class TSEQ>
|
||||
concept typeseq = requires (TSEQ seq)
|
||||
{
|
||||
// TSEQ can invoke a function with a Types<TYPES> subclass
|
||||
[]<typename...TYPES>(Types<TYPES...>&){/*placeholder*/}(seq);
|
||||
};
|
||||
#endif ///////////////////////////////////////////////////////////////////////////////////////////////////(End) : new generic library functions
|
||||
|
||||
|
||||
template<template<class> class BUILD, typeseq TSEQ>
|
||||
class FunTrampoline
|
||||
{
|
||||
using AllElm = ElmTypes<TSEQ>;
|
||||
using Indices = AllElm::Idx;
|
||||
using Builder = BUILD<TSEQ>;
|
||||
using Functor = Builder::Functor;
|
||||
using Table = std::array<Functor, AllElm::SIZ>;
|
||||
|
||||
template<size_t...idx>
|
||||
static constexpr Table
|
||||
buildFunctors (std::index_sequence<idx...>)
|
||||
{
|
||||
return {Builder().template build<idx>()...};
|
||||
}
|
||||
|
||||
static constexpr Table functors = buildFunctors (Indices{});
|
||||
|
||||
public:
|
||||
static constexpr size_t size() { return functors.size(); }
|
||||
static constexpr size_t memSiz() { return sizeof(functors); }
|
||||
|
||||
static Functor const&
|
||||
dispatch (size_t idx)
|
||||
{
|
||||
ENSURE (idx < functors.size());
|
||||
return functors[idx];
|
||||
}
|
||||
|
||||
using ResType = _Fun<Functor>::Ret;
|
||||
};
|
||||
|
||||
|
||||
#if false ///////////////////////////////////////////////////////////////////////////////////////////////////TODO : new generic library functions
|
||||
template<typename T>
|
||||
using isConst = std::is_const<remove_reference_t<T>>;
|
||||
template<typename T>
|
||||
using isLRef = std::is_lvalue_reference<T>;
|
||||
|
||||
|
||||
template<class SEQ>
|
||||
using Any_Const = ElmTypes<SEQ>::template OrAll<isConst>;
|
||||
|
||||
template<class SEQ>
|
||||
using All_LRef = ElmTypes<SEQ>::template AndAll<isLRef>;
|
||||
|
||||
template<class SEQ>
|
||||
static constexpr bool Any_Const_v = Any_Const<SEQ>::value;
|
||||
|
||||
template<class SEQ>
|
||||
static constexpr bool All_LRef_v = All_LRef<SEQ>::value;
|
||||
|
||||
|
||||
|
||||
|
||||
template<typename X, typename U, typename...TYPES>
|
||||
struct ProbeCommonType
|
||||
{
|
||||
|
||||
template<typename T1, typename T2>
|
||||
using Reconciled = decltype(true ? std::declval<T1>() : std::declval<T2>());
|
||||
|
||||
|
||||
template<typename RES>
|
||||
struct FoundResult
|
||||
: std::true_type
|
||||
{
|
||||
using Type = RES;
|
||||
};
|
||||
struct NoResultFound
|
||||
: std::false_type
|
||||
{ };
|
||||
|
||||
template<class T1
|
||||
,class T2
|
||||
,typename = Reconciled<T1,T2>>
|
||||
static FoundResult<Reconciled<T1,T2>>
|
||||
check(int);
|
||||
|
||||
template<class,class>
|
||||
static NoResultFound
|
||||
check(...);
|
||||
|
||||
using FirstResult = decltype(check<X,U>(0));
|
||||
|
||||
template<bool success, typename...TS>
|
||||
struct Join
|
||||
{
|
||||
using Result = FirstResult;
|
||||
};
|
||||
|
||||
template<typename T1, typename...TS>
|
||||
struct Join<true, T1,TS...>
|
||||
{
|
||||
//wenn RES erfolgreich
|
||||
using CT = FirstResult::Type;
|
||||
using Result = ProbeCommonType<CT,T1,TS...>::Result;
|
||||
};
|
||||
|
||||
using Result = Join<FirstResult::value, TYPES...>::Result;
|
||||
};
|
||||
|
||||
|
||||
|
||||
template<typename...TYPES>
|
||||
struct CommonResult
|
||||
: ProbeCommonType<TYPES...>::Result
|
||||
{ };
|
||||
|
||||
template<typename T1>
|
||||
struct CommonResult<T1>
|
||||
: CommonResult<T1,T1>
|
||||
{ };
|
||||
|
||||
template<>
|
||||
struct CommonResult<>
|
||||
: std::false_type
|
||||
{ };
|
||||
|
||||
template<typename...TYPES>
|
||||
struct CommonResult<Types<TYPES...>>
|
||||
: CommonResult<TYPES...>
|
||||
{ };
|
||||
|
||||
template<typename...TYPES>
|
||||
using CommonResult_t = CommonResult<TYPES...>::Type;
|
||||
|
||||
template<typename...TYPES>
|
||||
static constexpr bool has_CommonResult_v = CommonResult<TYPES...>::value;
|
||||
|
||||
|
||||
template<typename...TYPES>
|
||||
concept reconcilable = requires
|
||||
{
|
||||
typename CommonResult<TYPES...>::Type;
|
||||
};
|
||||
|
||||
template<typename...TYPES>
|
||||
concept reconcilable_ref = reconcilable<TYPES...>
|
||||
and isLRef_v<CommonResult_t<TYPES...>>;
|
||||
|
||||
template<typename...TYPES>
|
||||
struct CommonResultTraits
|
||||
{
|
||||
static_assert (reconcilable<TYPES...>, "unable to reconcile these types");
|
||||
|
||||
using CommonVal = std::common_type_t<TYPES...>;
|
||||
using ResType = CommonResult_t<TYPES...>;
|
||||
//NOTE: std::common_type decays (strips CV and ref)
|
||||
static constexpr bool makeConst = isConst_v<ResType>;
|
||||
static constexpr bool exposeRef = isLRef_v<ResType>;
|
||||
};
|
||||
#endif ///////////////////////////////////////////////////////////////////////////////////////////////////(End) : new generic library functions
|
||||
|
||||
|
||||
|
||||
template<class TUP>
|
||||
class TupleIndexAdaptor
|
||||
: public TUP
|
||||
{
|
||||
template<typeseq SEQ>
|
||||
struct SubscriptBuilder
|
||||
{
|
||||
using Tuple = RefTraits<TUP>::value_type;
|
||||
using AddRefs = ElmTypes<SEQ>::template Apply<std::add_lvalue_reference_t>;
|
||||
using ResType = CommonResult_t<AddRefs>;
|
||||
using Functor = ResType(*)(Tuple&);
|
||||
|
||||
template<size_t idx>
|
||||
constexpr auto
|
||||
build()
|
||||
{
|
||||
return [](Tuple& tuple) -> ResType
|
||||
{
|
||||
return getElm<idx> (tuple);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
using TypeSeq = ElmTypes<TUP>::Seq;
|
||||
using Accessor = FunTrampoline<SubscriptBuilder, TypeSeq>;
|
||||
public:
|
||||
using ResType = Accessor::ResType;
|
||||
using reference = ResType;
|
||||
using value_Type = RefTraits<ResType>::value_type;
|
||||
|
||||
template<typename T>
|
||||
TupleIndexAdaptor(T&& dataSrc)
|
||||
: TUP{forward<T> (dataSrc)}
|
||||
{ }
|
||||
|
||||
using TUP::TUP;
|
||||
|
||||
static constexpr size_t size() { return Accessor::size(); }
|
||||
|
||||
ResType
|
||||
operator[] (size_t idx)
|
||||
{
|
||||
return Accessor::dispatch(idx)(*this);
|
||||
}
|
||||
};
|
||||
|
||||
template<class TUP>
|
||||
TupleIndexAdaptor(TUP) -> TupleIndexAdaptor<std::decay_t<TUP>>;
|
||||
}}
|
||||
|
||||
using namespace lib::meta;
|
||||
|
||||
template<typeseq SEQ>
|
||||
struct Stringify
|
||||
{
|
||||
using Tuple = ElmTypes<SEQ>::Tup;
|
||||
using Functor = string(*)(Tuple const&);
|
||||
|
||||
template<size_t idx>
|
||||
constexpr auto
|
||||
build()
|
||||
{
|
||||
using ElmT = std::tuple_element_t<idx, Tuple>;
|
||||
|
||||
return [](Tuple const& tuple)
|
||||
{
|
||||
return util::toString<ElmT> (getElm<idx> (tuple));
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
struct Uber { };
|
||||
struct Untr : Uber { };
|
||||
struct Unta : Uber { };
|
||||
|
||||
|
||||
int
|
||||
main (int, char**)
|
||||
|
|
@ -57,20 +313,10 @@ main (int, char**)
|
|||
using Arr = std::array<int,3>;
|
||||
using Hetero = lib::HeteroData<int,string>::Chain<short>::ChainExtent<bool,lib::meta::Nil>::ChainType;
|
||||
|
||||
SHOW_EXPR((lib::meta::tuple_sized<Tup> ))
|
||||
SHOW_EXPR((lib::meta::tuple_sized<Arr> ))
|
||||
SHOW_EXPR((lib::meta::tuple_sized<Hetero> ))
|
||||
SHOW_EXPR((lib::meta::tuple_sized<int> ))
|
||||
|
||||
SHOW_EXPR((lib::meta::tuple_element_accessible<Tup,0>))
|
||||
// SHOW_EXPR((lib::meta::tuple_element_accessible<Tup,2>))
|
||||
SHOW_EXPR((lib::meta::tuple_element_accessible<Hetero,0>))
|
||||
SHOW_EXPR((lib::meta::tuple_accessible<Tup>))
|
||||
SHOW_EXPR((lib::meta::tuple_accessible<Hetero>))
|
||||
|
||||
SHOW_EXPR((lib::meta::tuple_like<Tup> ))
|
||||
SHOW_EXPR((lib::meta::tuple_like<Arr> ))
|
||||
SHOW_EXPR((lib::meta::tuple_like<Hetero> ))
|
||||
SHOW_EXPR((lib::meta::tuple_like<string> ))
|
||||
SHOW_EXPR((lib::meta::tuple_like<int> ))
|
||||
|
||||
show<Tup>();
|
||||
|
|
@ -78,25 +324,112 @@ main (int, char**)
|
|||
show<Hetero>();
|
||||
show<int>();
|
||||
|
||||
SHOW_EXPR((std::tuple_size_v<const Tup>))
|
||||
using Elm1 = std::tuple_element_t<1, const Tup>;
|
||||
SHOW_TYPE(Elm1)
|
||||
using TupConstSeq = lib::meta::ElmTypes<const Tup>::Seq;
|
||||
SHOW_TYPE(TupConstSeq)
|
||||
using TupStringy = FunTrampoline<Stringify, ElmTypes<Tup>::Seq>;
|
||||
SHOW_EXPR(TupStringy::size())
|
||||
|
||||
using T1 = decltype(lib::meta::getElm<0> (std::declval<Tup>()));
|
||||
SHOW_TYPE(T1)
|
||||
using T2 = decltype(lib::meta::getElm<0> (std::declval<Tup&>()));
|
||||
SHOW_TYPE(T2)
|
||||
using T3 = decltype(lib::meta::getElm<0> (std::declval<Tup const&>()));
|
||||
SHOW_TYPE(T3)
|
||||
Tup tup23{2,3};
|
||||
Tup tup42{4,2};
|
||||
SHOW_TYPE(decltype(std::get<1>(tup42)))
|
||||
SHOW_EXPR(std::get<1>(tup42))
|
||||
SHOW_EXPR(TupStringy::dispatch(0)(tup23))
|
||||
SHOW_EXPR(TupStringy::dispatch(0)(tup42))
|
||||
SHOW_EXPR(TupStringy::dispatch(1)(tup23))
|
||||
SHOW_EXPR(TupStringy::dispatch(1)(tup42))
|
||||
|
||||
using H1 = decltype(lib::meta::getElm<4> (std::declval<Hetero>()));
|
||||
SHOW_TYPE(H1)
|
||||
using H2 = decltype(lib::meta::getElm<4> (std::declval<Hetero&>()));
|
||||
SHOW_TYPE(H2)
|
||||
using H3 = decltype(lib::meta::getElm<4> (std::declval<Hetero const&>()));
|
||||
SHOW_TYPE(H3)
|
||||
using T1 = Types<int const&,long&, char&>;
|
||||
using T2 = Types<int*,void*>;
|
||||
using T3 = Types<int,string>;
|
||||
SHOW_EXPR((has_CommonResult_v<T1>))
|
||||
SHOW_EXPR((has_CommonResult_v<T2>))
|
||||
SHOW_EXPR((has_CommonResult_v<T3>))
|
||||
SHOW_EXPR((has_CommonResult_v<>))
|
||||
SHOW_EXPR((has_CommonResult_v<float*>))
|
||||
SHOW_EXPR((has_CommonResult_v<float*,void* const>))
|
||||
|
||||
SHOW_TYPE(CommonResult_t<T1>)
|
||||
SHOW_TYPE(CommonResult_t<T2>)
|
||||
SHOW_TYPE(CommonResult_t<float*>)
|
||||
using CoTx = CommonResult_t<float*,float const*>;
|
||||
SHOW_TYPE(CoTx)
|
||||
|
||||
|
||||
TupleIndexAdaptor<Tup> idxAdapt{tup42};
|
||||
SHOW_EXPR(sizeof(tup42))
|
||||
SHOW_EXPR(sizeof(idxAdapt))
|
||||
SHOW_TYPE(TupleIndexAdaptor<Tup>::ResType)
|
||||
SHOW_EXPR(idxAdapt.size())
|
||||
SHOW_EXPR(idxAdapt[0])
|
||||
SHOW_EXPR(idxAdapt[1])
|
||||
SHOW_EXPR(getElm<0>(static_cast<Tup&> (idxAdapt)))
|
||||
SHOW_EXPR(getElm<1>(static_cast<Tup&> (idxAdapt)))
|
||||
SHOW_TYPE(decltype(idxAdapt[1]))
|
||||
|
||||
using Tup2 = std::tuple<int,int&, int>;
|
||||
int ii{23};
|
||||
Tup2 tup21{-1,ii,55};
|
||||
TupleIndexAdaptor idxApt2{tup21};
|
||||
SHOW_TYPE(decltype(tup21))
|
||||
using IdxApt2 = decltype(idxApt2);
|
||||
SHOW_TYPE(IdxApt2)
|
||||
SHOW_TYPE(IdxApt2::ResType)
|
||||
SHOW_EXPR(idxApt2[0])
|
||||
SHOW_EXPR(idxApt2[1])
|
||||
SHOW_EXPR(idxApt2[2])
|
||||
SHOW_EXPR(++idxApt2[0])
|
||||
SHOW_EXPR(idxApt2[1]++)
|
||||
SHOW_EXPR(++++idxApt2[2])
|
||||
SHOW_EXPR(tup21)
|
||||
SHOW_EXPR(Tup2(idxApt2))
|
||||
SHOW_EXPR(ii)
|
||||
|
||||
using TupIt = lib::IterableDecorator<lib::IdxStoreCore<IdxApt2>>;
|
||||
TupIt tupIt{move(idxApt2)};
|
||||
SHOW_EXPR( bool(tupIt) )
|
||||
SHOW_EXPR( *tupIt )
|
||||
SHOW_TYPE(decltype(*tupIt))
|
||||
++*tupIt;
|
||||
--ii;
|
||||
SHOW_EXPR( *tupIt )
|
||||
SHOW_EXPR(util::join(tupIt, "▶▶"))
|
||||
|
||||
SHOW_EXPR(( reconcilable<long,string> ))
|
||||
SHOW_EXPR(( reconcilable<long&,string&> ))
|
||||
SHOW_EXPR(( reconcilable<long,short> ))
|
||||
SHOW_EXPR(( reconcilable<long&,short&> ))
|
||||
SHOW_EXPR(( reconcilable_ref<long,string> ))
|
||||
SHOW_EXPR(( reconcilable_ref<long&,string&> ))
|
||||
SHOW_EXPR(( reconcilable_ref<long,short> ))
|
||||
SHOW_EXPR(( reconcilable_ref<long&,const long&> ))
|
||||
using TX = CommonResult_t<long&,const long&>;
|
||||
SHOW_TYPE(TX)
|
||||
SHOW_EXPR(( reconcilable<float*,void*> ))
|
||||
SHOW_EXPR(( reconcilable<void,void*> ))
|
||||
SHOW_EXPR(( reconcilable<float*&,void*&> ))
|
||||
using TX2 = CommonResult_t<float*&,float*&>;
|
||||
SHOW_TYPE(TX2)
|
||||
SHOW_EXPR(( reconcilable<Uber,Unta> ))
|
||||
SHOW_EXPR(( reconcilable<Untr,Unta> ))
|
||||
SHOW_EXPR(( reconcilable<Untr&,Unta&> ))
|
||||
SHOW_EXPR(( reconcilable<Uber&,Unta&> ))
|
||||
SHOW_EXPR(( reconcilable_ref<Uber&,Unta&> ))
|
||||
SHOW_EXPR(( reconcilable_ref<Uber&,Unta> ))
|
||||
SHOW_EXPR(( reconcilable<Uber&,Unta> ))
|
||||
SHOW_EXPR(( reconcilable<Uber,Untr&> ))
|
||||
using TX3 = CommonResult_t<Uber&,Unta>;
|
||||
SHOW_TYPE(TX3)
|
||||
using TX4 = CommonResult_t<Unta&, const Uber&>;
|
||||
SHOW_TYPE(TX4)
|
||||
SHOW_EXPR(( reconcilable<Uber&,Unta&,Untr&> ))
|
||||
using TX5 = CommonResult_t<const Uber&,Unta&,Untr&>;
|
||||
SHOW_TYPE(TX5)
|
||||
using TX6 = CommonResult_t<const int&,uint16_t,int64_t>;
|
||||
SHOW_TYPE(TX6)
|
||||
SHOW_EXPR(( CommonResult<int,string>::value ))
|
||||
SHOW_EXPR(( CommonResult<int,string,long>::value ))
|
||||
SHOW_EXPR(( CommonResult<const int,int&>::value ))
|
||||
SHOW_EXPR(( reconcilable<const int&,uint16_t,int64_t> ))
|
||||
SHOW_EXPR(( reconcilable<const Uber&,Unta&,Untr&> ))
|
||||
SHOW_EXPR(( reconcilable<Uber&,const Unta&,Untr&> ))
|
||||
|
||||
cout << "\n.gulp." <<endl;
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -100,6 +100,119 @@ namespace util {
|
|||
|
||||
|
||||
|
||||
|
||||
namespace { // helper for printing type diagnostics
|
||||
|
||||
template<typename X>
|
||||
struct TypeDiagnostics
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = "";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<const X>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "const ";
|
||||
static constexpr auto postfix = "";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X&>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = "&";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X&&>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = " &&";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X const&>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = " const&";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X const&&>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "const ";
|
||||
static constexpr auto postfix = " &&";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X *>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = " *";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<const X *>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "const ";
|
||||
static constexpr auto postfix = " *";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<const X * const>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "const ";
|
||||
static constexpr auto postfix = " * const";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X * const>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = " * const";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X * const *>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = " * const *";
|
||||
};
|
||||
}//(End)decoration helper
|
||||
|
||||
/** diagnostic type output, including const and similar adornments
|
||||
* @warning operates after-the-fact and relies on mangled type names
|
||||
* plus several heuristics. Output might thus not be entirely correct,
|
||||
* especially when several levels of const, pointer and references are involved.
|
||||
* If in doubt, place the test::TypeDebugger<T> to reveal the type as the compiler sees it.
|
||||
* @remarks the function lib::meta::typeStr was deliberately written such as to remove type adornments
|
||||
* and does not work on all kinds of reference. This helper complements this and
|
||||
* attempts to preserve const-ness, pointer and reference decoration.
|
||||
*/
|
||||
template<typename X>
|
||||
inline std::string
|
||||
showType()
|
||||
{
|
||||
using Case = TypeDiagnostics<X>;
|
||||
using Type = Case::Type;
|
||||
|
||||
return Case::prefix
|
||||
+ lib::meta::humanReadableTypeID (typeid(Type).name())
|
||||
+ Case::postfix;
|
||||
}
|
||||
|
||||
template<typename...TS>
|
||||
inline std::string
|
||||
showTypes()
|
||||
{
|
||||
return "<| " + ((showType<TS>()+", ") + ... + "|>");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
namespace {
|
||||
/** toggle to prefer specialisation with direct lexical conversion */
|
||||
template<typename X>
|
||||
|
|
|
|||
|
|
@ -761,7 +761,11 @@ namespace lib {
|
|||
, i_{0}
|
||||
{ }
|
||||
|
||||
IdxStoreCore() = default;
|
||||
IdxStoreCore()
|
||||
: CON{}
|
||||
, i_{CON::size()}
|
||||
{ } // explicitly ensure that iterator is empty
|
||||
|
||||
// copy and assignment acceptable (warning!)
|
||||
|
||||
|
||||
|
|
@ -775,8 +779,8 @@ namespace lib {
|
|||
decltype(auto)
|
||||
yield() const
|
||||
{
|
||||
return CON::operator[] (i_);
|
||||
}
|
||||
return util::unConst(*this).operator[] (i_);
|
||||
} // rationale: const iterator ≠ const_iterator
|
||||
|
||||
void
|
||||
iterNext()
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@
|
|||
#include "lib/error.hpp"
|
||||
#include "lib/uninitialised-storage.hpp"
|
||||
#include "lib/meta/duck-detector.hpp"
|
||||
#include "lib/meta/common-result.hpp"
|
||||
#include "lib/meta/function.hpp"
|
||||
#include "lib/meta/trait.hpp"
|
||||
#include "lib/item-wrapper.hpp"
|
||||
|
|
@ -305,7 +306,7 @@ namespace lib {
|
|||
template<class SRC>
|
||||
struct _DecoratorTraits<SRC, enable_if<shall_wrap_STL_Iter<SRC>>>
|
||||
{
|
||||
static_assert (not std::is_rvalue_reference<SRC>::value,
|
||||
static_assert (not std::is_rvalue_reference_v<SRC>,
|
||||
"container needs to exist elsewhere during the lifetime of the iteration");
|
||||
using SrcIter = iter_explorer::StlRange<SRC>;
|
||||
using SrcVal = SrcIter::value_type;
|
||||
|
|
@ -416,19 +417,15 @@ namespace lib {
|
|||
using ResIter = _DecoratorTraits<RES>::SrcIter;
|
||||
using SrcYield = iter::Yield<SRC>;
|
||||
using ResYield = iter::Yield<ResIter>;
|
||||
using _CommonT = meta::CommonResultYield<SrcYield,ResYield>;
|
||||
using _CommonT = meta::CommonResult<SrcYield,ResYield>;
|
||||
static constexpr bool can_reconcile = _CommonT::value;
|
||||
static constexpr bool isRefResult = _CommonT::isRef;
|
||||
|
||||
static_assert (can_reconcile,
|
||||
"source iterator and result from the expansion must yield compatible values");
|
||||
static_assert (is_const_v<SrcYield> == is_const_v<ResYield>,
|
||||
"source and expanded types differ in const-ness");
|
||||
|
||||
using YieldRes = _CommonT::ResType;
|
||||
using value_type = _CommonT::value_type;
|
||||
using reference = _CommonT::reference;
|
||||
using pointer = _CommonT::pointer;
|
||||
using YieldRes = _CommonT::Type;
|
||||
};
|
||||
|
||||
}//(End) IterExplorer traits
|
||||
|
|
@ -749,9 +746,9 @@ namespace lib {
|
|||
|
||||
/** @note result type bindings based on a common type of source and expanded result */
|
||||
using YieldRes = _Trait::YieldRes;
|
||||
using value_type = _Trait::value_type;
|
||||
using reference = _Trait::reference;
|
||||
using pointer = _Trait::pointer;
|
||||
using value_type = meta::RefTraits<YieldRes>::value_type;
|
||||
using reference = meta::RefTraits<YieldRes>::reference;
|
||||
using pointer = meta::RefTraits<YieldRes>::pointer;
|
||||
|
||||
|
||||
bool
|
||||
|
|
|
|||
236
src/lib/meta/common-result.hpp
Normal file
236
src/lib/meta/common-result.hpp
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
COMMON-RESULT.hpp - reconciled return types
|
||||
|
||||
Copyright (C)
|
||||
2024,2026 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 common-result.hpp
|
||||
** Determine a common result type for combined functors.
|
||||
** This header defines a metafunction to determine if, and how the return types
|
||||
** of several functors or accessors can be combined. This task becomes relevant
|
||||
** when building exploration strategies defined over a functional datastructure.
|
||||
** A notable example happens when performing a _tree expansion_ from within a
|
||||
** [iterator pipeline](\ref iter-explorer.hpp) or when mapping an aggregating
|
||||
** functor onto a [tuple of values](\ref tuple-concept.hpp).
|
||||
**
|
||||
** The basic technique to find such a _reconciled type_ is to rely on the
|
||||
** _ternary operator_ (`test? yes_expr : no_expr;`), which is specified to
|
||||
** great detail in the C++ standard. However, the STDLIB provides only two
|
||||
** tools tailored for specific cases:
|
||||
** - std::common_type is what you can use to _store_ such a reconciled result,
|
||||
** and includes _decaying_ the type.
|
||||
** - std::common_reference is oriented towards passing of references,
|
||||
** and includes elaborate mapping logic for mixture of RValues and LValues.
|
||||
**
|
||||
** So there is a need to expose the foundational logic for type reconciliation,
|
||||
** without any special adaptation rules. Furthermore, direct support for passing
|
||||
** the selection of types in the form of a [Type Sequence](\ref typelist.hpp)
|
||||
** is included.
|
||||
**
|
||||
** @see CommonResult_test
|
||||
** @see lib::iter_explorer::Expander
|
||||
** @see tuple-idx-adaptor.hpp
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LIB_META_COMMON_RESULT_H
|
||||
#define LIB_META_COMMON_RESULT_H
|
||||
|
||||
|
||||
#include "lib/meta/trait.hpp"
|
||||
#include "lib/meta/typelist.hpp"
|
||||
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta {
|
||||
|
||||
namespace detail {// Implementation: reconcile N return types...
|
||||
|
||||
/** @internal helper to find a reconciled result type.
|
||||
* This is the technical part, and relies on a SFINAE technique:
|
||||
* The test succeeds, if a ternary operator with both type results
|
||||
* can be instantiated in the signature of a `check` template.
|
||||
*
|
||||
* Furthermore, the reconciliation for a pair of types can be
|
||||
* generalised to N types by gradually reconciling the following
|
||||
* types with the already established result.
|
||||
*
|
||||
* @remark 2026 the GNU Libstdc++ relies on precisely the same
|
||||
* technique, which implies that this approach works reliably.
|
||||
*/
|
||||
template<typename X, typename U, typename...TYPES>
|
||||
class _ProbeCommonType
|
||||
{
|
||||
/******************************************************************************//**
|
||||
* Core of the type reconciliation logic: determine the _common type_.
|
||||
* The implementation relies on the **ternary operator** — the C++ language
|
||||
* specifies a detailed and elaborate algorithm for this operator to reconcile
|
||||
* the results of the "true" and "false" parts of `test? true_part : false_part`;
|
||||
* such an expression compiles if and only if these criteria are met and a common
|
||||
* type can be determined. The results of this procedure are quite solid, and
|
||||
* satisfactory in most usage situations. A mixture of references, values const
|
||||
* and volatile is handled properly. Only some rare corner cases are not covered,
|
||||
* like upcasting two subclasses to a common interface (actually in some cases,
|
||||
* involving multiple inheritance, a single authoritative solution for this
|
||||
* special problem does not exist, and thus these cases were left out
|
||||
* from the language spec).
|
||||
*/
|
||||
template<typename T1, typename T2>
|
||||
using Reconciled = decltype(true ? std::declval<T1>() : std::declval<T2>());
|
||||
|
||||
|
||||
/** Marker type: successful test */
|
||||
template<typename RES>
|
||||
struct FoundResult
|
||||
: std::true_type
|
||||
{
|
||||
using Type = RES;
|
||||
};
|
||||
struct NoResultFound
|
||||
: std::false_type
|
||||
{ };
|
||||
|
||||
|
||||
/** SFINAE setup to probe for a reconciled type */
|
||||
template<class T1
|
||||
,class T2
|
||||
,typename = Reconciled<T1,T2>>
|
||||
static FoundResult<Reconciled<T1,T2>>
|
||||
check(int);
|
||||
|
||||
template<class,class>
|
||||
static NoResultFound
|
||||
check(...);
|
||||
|
||||
/** perform the reconciliation test */
|
||||
using FirstResult = decltype(check<X,U>(42));
|
||||
|
||||
|
||||
/** Helper: Join results of pairwise reconciliation
|
||||
* to extend the result to a sequence of types */
|
||||
template<bool success, typename...TS>
|
||||
struct Join
|
||||
{
|
||||
using Result = FirstResult;
|
||||
};
|
||||
|
||||
template<typename TX, typename...TS>
|
||||
struct Join<true, TX,TS...> ///<@note matches when FirstResult was successful
|
||||
{
|
||||
using CT = FirstResult::Type;
|
||||
using Result = _ProbeCommonType<CT,TX,TS...>::Result;
|
||||
};
|
||||
|
||||
|
||||
public:
|
||||
/** Retrieve the overall result of this metafunction */
|
||||
using Result = Join<FirstResult::value, TYPES...>::Result;
|
||||
};
|
||||
|
||||
}//(End)implementation: type reconciliation
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Metafunction to find a _common result type_.
|
||||
* @tparam TYPES a variadic sequence of types to be reconciled.
|
||||
* Finding such a common result type can be relevant when results
|
||||
* from several functors shall be combined, e.g. when mapping and
|
||||
* aggregating results of a function application to a tuple.
|
||||
* It is important to note that such a function may return arbitrary
|
||||
* types, either by value or by reference, and possibly marked as const.
|
||||
*
|
||||
* \par Interface
|
||||
* The `struct` CommonResult itself is a std::bool_constant that
|
||||
* indicates if the given types can be reconciled. When successful,
|
||||
* a nested typedef `Type` is present and aliased to a type that
|
||||
* can represent all given result types as a function return.
|
||||
*
|
||||
* @remarks
|
||||
* - the C++ standard offers similar functionality as
|
||||
* + std::common_type<TYPES...>
|
||||
* + std::common_reference<TYPES...>
|
||||
* - however, the standard solution, while based on the same logic
|
||||
* as this implementation, adds additional transformations, like
|
||||
* decaying the type (for std::common_type) or handling various
|
||||
* corner cases with RValue references. The specification of
|
||||
* expected behaviour is quite elaborate for those cases
|
||||
* - the implementation presented here is focused on _returning_
|
||||
* results from a function and thus relies _solely_ on the
|
||||
* logic of the ternary operator (`? : `)
|
||||
* - when several LValue-reference types are given, the result
|
||||
* is only a reference when all results can be presented safely
|
||||
* through such a reference; in most cases the result falls back
|
||||
* to returning by-value.
|
||||
* @note Concepts are provided to require that a selection of types
|
||||
* is reconcilable, or even reconcilable into a reference type.
|
||||
* @see CommonResult_test
|
||||
*/
|
||||
template<typename...TYPES>
|
||||
struct CommonResult
|
||||
: detail::_ProbeCommonType<TYPES...>::Result
|
||||
{ };
|
||||
|
||||
template<typename T1>
|
||||
struct CommonResult<T1>
|
||||
: CommonResult<T1,T1>
|
||||
{ };
|
||||
|
||||
template<>
|
||||
struct CommonResult<>
|
||||
: std::false_type
|
||||
{ };
|
||||
|
||||
/** Specialisation to work transparently also on a type sequence,
|
||||
* passed-in as a single template parameter. */
|
||||
template<typename...TYPES>
|
||||
struct CommonResult<Types<TYPES...>>
|
||||
: CommonResult<TYPES...>
|
||||
{ };
|
||||
|
||||
|
||||
|
||||
template<typename...TYPES>
|
||||
using CommonResult_t = CommonResult<TYPES...>::Type;
|
||||
|
||||
template<typename...TYPES>
|
||||
static constexpr bool has_CommonResult_v = CommonResult<TYPES...>::value;
|
||||
|
||||
|
||||
|
||||
template<typename...TYPES>
|
||||
concept reconcilable = requires
|
||||
{
|
||||
typename CommonResult<TYPES...>::Type;
|
||||
};
|
||||
|
||||
template<typename...TYPES>
|
||||
concept reconcilable_ref = reconcilable<TYPES...>
|
||||
and isLRef_v<CommonResult_t<TYPES...>>;
|
||||
|
||||
|
||||
|
||||
template<typename...TYPES>
|
||||
struct CommonResultTraits
|
||||
{
|
||||
static_assert (reconcilable<TYPES...>, "unable to reconcile these types");
|
||||
|
||||
using CommonVal = std::common_type_t<TYPES...>;
|
||||
using ResType = CommonResult_t<TYPES...>;
|
||||
//NOTE: std::common_type decays (strips CV and ref)
|
||||
static constexpr bool makeConst = isConst_v<ResType>;
|
||||
static constexpr bool exposeRef = isLRef_v<ResType>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
}} // namespace lib::meta
|
||||
#endif /*LIB_META_COMMON_RESULT_H*/
|
||||
125
src/lib/meta/fun-trampoline.hpp
Normal file
125
src/lib/meta/fun-trampoline.hpp
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
FUN-TRAMPOLINE.hpp - generate static functor table
|
||||
|
||||
Copyright (C)
|
||||
2026 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 fun-trampoline.hpp
|
||||
** Build a trampoline table with functors.
|
||||
** This Metaprogramming helper uses a _builder template_ to fill an embedded
|
||||
** static dispatcher table with generated functors. These can then be accessed
|
||||
** by index, typically to be invoked.
|
||||
**
|
||||
** The setup works entirely on a static level, so that any mention of some
|
||||
** FunTrampoline template instantiation causes the compiler to populate the
|
||||
** corresponding functor table. This arrangement can be used, for example,
|
||||
** to auto-generate an index accessor for a specific tuple, so that a
|
||||
** runtime subscript or iteration can be derived. Obviously doing so
|
||||
** requires the types in the tuple to be [reconcilable](\ref common-type.hpp),
|
||||
** so that a generic functor can be defined to handle all fields in the tuple
|
||||
** based on a common generic scheme. Without that, tuples can in general
|
||||
** only be iterated and transformed by compile-time metaprogramming.
|
||||
**
|
||||
** @see FunTrampoline_test
|
||||
** @see CommonResult_test
|
||||
** @see tuple-idx-adaptor.hpp
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LIB_META_FUN_TRAMPOLINE_H
|
||||
#define LIB_META_FUN_TRAMPOLINE_H
|
||||
|
||||
|
||||
#include "include/logging.h"
|
||||
#include "lib/meta/function.hpp"
|
||||
#include "lib/meta/variadic-helper.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <array>
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta {
|
||||
|
||||
/**
|
||||
* FunTrampoline relies on a helper template
|
||||
* to be used as _trampoline builder_ to generate
|
||||
* a sequence of functors that can be placed into a
|
||||
* static dispatcher table. Thus, a trampoline_builder
|
||||
* - must be default constructible
|
||||
* - must define a Functor type...
|
||||
* - that can be used as array element
|
||||
* - and must provide a templated build<idx>() function
|
||||
*/
|
||||
template<class B>
|
||||
concept trampoline_builder = requires(B builder)
|
||||
{
|
||||
typename B::Functor;
|
||||
std::array<typename B::Functor, 1>{};
|
||||
requires std::default_initializable<B>;
|
||||
{ builder.template build<0>() } -> std::convertible_to<typename B::Functor>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A static functor table, populated automatically.
|
||||
* @tparam BUILD helper Template to generate functors
|
||||
* that are placed into a static table
|
||||
* @tparam TSEQ sequence of types to instantiate
|
||||
* the functors with, iteratively.
|
||||
* @remarks
|
||||
* - the given type sequence is handled with the
|
||||
* ElmTypes helper template (see \ref variadic-helper.hpp)
|
||||
* - the BUILD template is instantiated with that \a TSEQ
|
||||
* - statically-for-each-index, the build<idx>() function
|
||||
* on the builder is invoked, which is expected to produce
|
||||
* an instance of BUILD::Functor
|
||||
* @warning as soon as some instance of this FunTrampoline
|
||||
* is used / referred, the compiler will emit the trampoline
|
||||
* table into static storage; the resulting increase of the
|
||||
* executable size is something to be aware of, when using
|
||||
* this construct for transient processing, driven by
|
||||
* actual argument types.
|
||||
*/
|
||||
template<template<class> class BUILD, typeseq TSEQ>
|
||||
requires trampoline_builder<BUILD<TSEQ>>
|
||||
class FunTrampoline
|
||||
{
|
||||
using AllElm = ElmTypes<TSEQ>;
|
||||
using Indices = AllElm::Idx;
|
||||
using Builder = BUILD<TSEQ>;
|
||||
using Functor = Builder::Functor;
|
||||
using Table = std::array<Functor, AllElm::SIZ>;
|
||||
|
||||
template<size_t...idx>
|
||||
static constexpr Table
|
||||
buildFunctors (std::index_sequence<idx...>)
|
||||
{
|
||||
return {Builder().template build<idx>()...};
|
||||
}
|
||||
|
||||
static constexpr Table functors = buildFunctors (Indices{});
|
||||
|
||||
public:
|
||||
static constexpr size_t size() { return functors.size(); }
|
||||
static constexpr size_t memSiz() { return sizeof(functors);}
|
||||
|
||||
static Functor const&
|
||||
dispatch (size_t idx)
|
||||
{
|
||||
ENSURE (idx < functors.size());
|
||||
return functors[idx];
|
||||
}
|
||||
|
||||
using ResType = _Fun<Functor>::Ret;
|
||||
};
|
||||
|
||||
}}// namespace lib::meta
|
||||
#endif /*LIB_META_FUN_TRAMPOLINE_H*/
|
||||
|
|
@ -103,13 +103,22 @@ namespace meta {
|
|||
using std::__or_;
|
||||
|
||||
template<typename T>
|
||||
static constexpr bool isConst_v = std::is_const_v<remove_reference_t<T>>;
|
||||
using isConst = std::is_const<remove_reference_t<T>>;
|
||||
template<typename T>
|
||||
static constexpr bool isLRef_v = std::is_lvalue_reference_v<T>;
|
||||
using isLRef = std::is_lvalue_reference<T>;
|
||||
template<typename T>
|
||||
static constexpr bool isRRef_v = std::is_rvalue_reference_v<T>;
|
||||
using isRRef = std::is_rvalue_reference<T>;
|
||||
template<typename T>
|
||||
static constexpr bool isRef_v = std::is_reference_v<T>;
|
||||
using isRef = std::is_reference<T>;
|
||||
|
||||
template<typename T>
|
||||
static constexpr bool isConst_v = isConst<T>::value;
|
||||
template<typename T>
|
||||
static constexpr bool isLRef_v = isLRef<T>::value;
|
||||
template<typename T>
|
||||
static constexpr bool isRRef_v = isRRef<T>::value;
|
||||
template<typename T>
|
||||
static constexpr bool isRef_v = isRef<T>::value;
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -105,13 +105,35 @@ namespace meta {
|
|||
and tuple_accessible<remove_cv_t<TUP>>;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Concept to require an instance of std::tuple<TYPES...>`
|
||||
* @note other than `concept` \ref tuple_like, this aims
|
||||
* explicitly at the implementation form STDLIB.
|
||||
*/
|
||||
template<class TUP>
|
||||
concept tup = requires (TUP tup)
|
||||
{
|
||||
// check that a function can be invoked with a std::tuple<TYPES>
|
||||
[]<typename...TYPES>(std::tuple<TYPES...>){/*placeholder*/}(tup);
|
||||
};
|
||||
|
||||
template<class TUP>
|
||||
struct is_Tuple : std::bool_constant<tup<TUP>> { };
|
||||
|
||||
template<class TUP>
|
||||
static constexpr bool is_Tuple_v = is_Tuple<TUP>::value;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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>>)
|
||||
requires tuple_like<std::remove_reference_t<TUP>>
|
||||
decltype(auto)
|
||||
getElm (TUP&& tup)
|
||||
{
|
||||
|
|
@ -169,7 +191,7 @@ namespace meta {
|
|||
using AndAll = ElmTypes<Apply<PRED>>::template Rebind<std::__and_>;
|
||||
|
||||
template<template<class> class PRED>
|
||||
using OrAll = ElmTypes<Apply<PRED>>::template Rebind<std::__or_>;
|
||||
using OrAny = ElmTypes<Apply<PRED>>::template Rebind<std::__or_>;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -221,7 +243,7 @@ namespace meta {
|
|||
* @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>>)
|
||||
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> )
|
||||
{
|
||||
|
|
@ -242,7 +264,7 @@ namespace meta {
|
|||
* 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>>)
|
||||
template<class TUP, class FUN> requires tuple_like<remove_reference_t<TUP>>
|
||||
constexpr void
|
||||
forEach (TUP&& tuple, FUN fun)
|
||||
{
|
||||
|
|
@ -267,7 +289,7 @@ namespace meta {
|
|||
* 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>>)
|
||||
template<class TUP, class FUN> requires tuple_like<remove_reference_t<TUP>>
|
||||
constexpr auto
|
||||
mapEach (TUP&& tuple, FUN fun)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -33,8 +33,23 @@
|
|||
** a generic Lambda for each element of a given tuple, which allows to write
|
||||
** generic code »for each tuple element«.
|
||||
**
|
||||
** @remark 2026-03 this header underwent some significant rework in recent years.
|
||||
** The Lumiera project had developed a `Tuple` type way before something similar
|
||||
** was considered for inclusion into the C++ language standard; flexible and
|
||||
** specifically typed storage records were used, in conjunction with LISP-style
|
||||
** metaprogramming (»Loki Typelists«), from the very start of the project.
|
||||
** Storing an arbitrary function, combined with a tuple of arguments, was a
|
||||
** major undertaking at those days, and required a huge body of support
|
||||
** functionality — which needed to be remoulded gradually, over the
|
||||
** following years, to become compatible with newer C++ features.
|
||||
** - basic handling, like access and compile-time iteration became trivial
|
||||
** - however, the project still relies on specific metaprogramming tools,
|
||||
** to simplify handling and transforming of variadic metafunctions.
|
||||
**
|
||||
** [tuple protocol]: https://en.cppreference.com/w/cpp/language/structured_binding.html#Case_2:_binding_a_type_implementing_the_tuple_operations
|
||||
** @see control::CommandDef usage example
|
||||
** @see variadic-helper.hpp
|
||||
** @see tuple-closure.hpp : usage example
|
||||
** @see feed-manifold.hpp : use of unified variadics and tuples
|
||||
** @see TupleHelper_test
|
||||
** @see typelist.hpp
|
||||
** @see function.hpp
|
||||
|
|
@ -49,9 +64,8 @@
|
|||
#include "lib/integral.hpp"
|
||||
#include "lib/meta/tuple-concept.hpp"
|
||||
#include "lib/meta/variadic-helper.hpp"
|
||||
#include "lib/meta/typeseq-util.hpp"
|
||||
#include "lib/meta/typelist.hpp"
|
||||
#include "lib/meta/typelist-util.hpp"
|
||||
#include "lib/meta/typeseq-util.hpp"
|
||||
#include "lib/meta/util.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
|
@ -59,30 +73,18 @@
|
|||
#include <functional>
|
||||
|
||||
|
||||
namespace util { // forward -> see lib/format-obj.hpp ...
|
||||
|
||||
template<typename TY>
|
||||
std::string toString (TY const&) noexcept;
|
||||
|
||||
template<typename X, typename COND>
|
||||
struct StringConv;
|
||||
}
|
||||
|
||||
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
|
||||
{ };
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace { // rebinding helper to create std::tuple from a type sequence
|
||||
|
||||
template<typename SEQ>
|
||||
|
|
@ -239,5 +241,85 @@ namespace meta {
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ======= Tuple toString rendering ======= */
|
||||
|
||||
/**
|
||||
* convenience function to dump a given tuple's contents, converted to string.
|
||||
* @note this is the basic functionality to place rendered data into a `ostringstream.`
|
||||
*/
|
||||
template<class TUP>
|
||||
inline void
|
||||
joinTuple (TUP const& tup, std::string delim, std::ostringstream& buffer)
|
||||
{
|
||||
forEachIDX<TUP> ([&](auto i)
|
||||
{
|
||||
using Elm = std::tuple_element_t<i, TUP>;
|
||||
if (i)
|
||||
buffer << delim;
|
||||
buffer << util::toString<Elm> (getElm<i> (tup));
|
||||
});
|
||||
}
|
||||
|
||||
template<class TUP>
|
||||
inline void
|
||||
joinTuple (TUP const& tup, std::ostringstream& buffer)
|
||||
{
|
||||
joinTuple (tup, ",");
|
||||
}
|
||||
|
||||
/** convenience function to join the tuple's contents into a single string.
|
||||
* @param delim optional delimiter to intersperse, defaults to `","`
|
||||
*/
|
||||
template<class TUP>
|
||||
inline std::string
|
||||
joinTuple (TUP const& tup, std::string delim =",")
|
||||
{
|
||||
std::ostringstream buffer;
|
||||
joinTuple (tup, delim, buffer);
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
template<class TUP>
|
||||
inline void
|
||||
joinTupleParen (TUP const& tup, std::ostringstream& buffer)
|
||||
{
|
||||
buffer << '(';
|
||||
joinTuple (tup, ",", buffer);
|
||||
buffer << ')';
|
||||
}
|
||||
|
||||
template<class TUP>
|
||||
inline std::string
|
||||
joinTupleParen (TUP const& tup)
|
||||
{
|
||||
std::ostringstream buffer;
|
||||
joinTupleParen (tup, buffer);
|
||||
return buffer.str();
|
||||
}
|
||||
}} // 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 {
|
||||
std::ostringstream buffer;
|
||||
buffer << "«"
|
||||
<< typeStr(tuple)
|
||||
<< "»──";
|
||||
lib::meta::joinTupleParen (tuple, buffer);
|
||||
return buffer.str();
|
||||
}
|
||||
catch(...) { return FAILURE_INDICATOR; }
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
#endif /*LIB_META_TUPLE_HELPER_H*/
|
||||
|
|
|
|||
131
src/lib/meta/tuple-idx-adaptor.hpp
Normal file
131
src/lib/meta/tuple-idx-adaptor.hpp
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
TUPLE-IDX-ADAPTOR.hpp - runtime-indexed access to tuples
|
||||
|
||||
Copyright (C)
|
||||
2026 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-idx-adaptor.hpp
|
||||
** Adaptor to provide access to tuple elements by runtime subscript.
|
||||
** Tuples are data records with distinct fields of different type; thus
|
||||
** most of indexing, iteration and transformation of tuples need to be
|
||||
** performed statically, at compile-time. However, under the proviso that
|
||||
** the types in a given tuple are somehow _reconcilable_, a dispatcher
|
||||
** table with access functors can be built, so that effectively all fields
|
||||
** can be handled by instances of a generic functor. This setup allows to
|
||||
** define a subscript `operator[]`, so that the suitable functor is picked
|
||||
** with runtime-index and applied to the tuple to yield the indicated field,
|
||||
** albeit converted into a _common type_.
|
||||
**
|
||||
** While this is certainly a very specific setup, and incurs some cost,
|
||||
** since the functor dispatcher tables must be prepared statically, such
|
||||
** a runtime-controlled index access allows for dynamic and data-driven
|
||||
** processing of tuple_like structures, which are fully type-safe,
|
||||
** compact in storage and easy to nest. Furthermore, run-time
|
||||
** processing of arguments passed to variadic functions
|
||||
** is enabled, providing a new kind of flexible API.
|
||||
**
|
||||
** @see TupleIdxAdaptor_test
|
||||
** @see FunTrampoline_test
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LIB_META_TUPLE_IDX_ADAPTOR_H
|
||||
#define LIB_META_TUPLE_IDX_ADAPTOR_H
|
||||
|
||||
|
||||
#include "include/logging.h"
|
||||
#include "lib/iter-adapter.hpp"
|
||||
#include "lib/meta/tuple-helper.hpp"
|
||||
#include "lib/meta/common-result.hpp"
|
||||
#include "lib/meta/fun-trampoline.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta {
|
||||
|
||||
template<class TUP>
|
||||
class TupleIndexAdaptor
|
||||
: public TUP
|
||||
{
|
||||
/** Helper to generate a dispatch table for
|
||||
* this specific instance of tuple element types */
|
||||
template<typeseq SEQ>
|
||||
struct SubscriptBuilder
|
||||
{
|
||||
static_assert (reconcilable<SEQ>
|
||||
,"A common run-time index accessor can only be built "
|
||||
"if all types in the tuple can be reconciled into a "
|
||||
"common type used for the access");
|
||||
|
||||
using Tuple = RefTraits<TUP>::value_type;
|
||||
using AddRefs = ElmTypes<SEQ>::template Apply<std::add_lvalue_reference_t>;
|
||||
using ResType = CommonResult_t<AddRefs>;
|
||||
using Functor = ResType(*)(Tuple&);
|
||||
|
||||
template<size_t idx>
|
||||
constexpr auto
|
||||
build()
|
||||
{
|
||||
return [](Tuple& tuple) -> ResType
|
||||
{
|
||||
return getElm<idx> (tuple);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
using TypeSeq = ElmTypes<TUP>::Seq;
|
||||
using Accessor = FunTrampoline<SubscriptBuilder, TypeSeq>;
|
||||
|
||||
public:
|
||||
using Tuple = TUP;
|
||||
using ResType = Accessor::ResType;
|
||||
using reference = ResType;
|
||||
using value_Type = RefTraits<ResType>::value_type;
|
||||
static constexpr bool isRefAccess = isLRef_v<ResType>;
|
||||
|
||||
|
||||
/** construct by passing a source tuple */
|
||||
template<typename T>
|
||||
TupleIndexAdaptor(T&& dataSrc)
|
||||
: TUP{forward<T> (dataSrc)}
|
||||
{ }
|
||||
|
||||
/// lift all constructors of the adapted tuple
|
||||
using TUP::TUP;
|
||||
|
||||
Tuple & tuple() { return *this; }
|
||||
Tuple const& tuple() const { return *this; }
|
||||
|
||||
operator std::string() const
|
||||
{
|
||||
return "iTup" + joinTupleParen (this->tuple());
|
||||
}
|
||||
|
||||
|
||||
/* === subscriptable === */
|
||||
|
||||
static constexpr size_t size() { return Accessor::size(); }
|
||||
|
||||
ResType
|
||||
operator[] (size_t idx)
|
||||
{
|
||||
return Accessor::dispatch(idx)(*this);
|
||||
}
|
||||
};
|
||||
|
||||
/// deduction guide
|
||||
template<class TUP>
|
||||
TupleIndexAdaptor(TUP) -> TupleIndexAdaptor<std::decay_t<TUP>>;
|
||||
|
||||
|
||||
}}// namespace lib::meta
|
||||
#endif /*LIB_META_TUPLE_IDX_ADAPTOR_H*/
|
||||
|
|
@ -115,5 +115,21 @@ namespace meta {
|
|||
using Seq = Types<>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Concept to require a generic type sequence `Types<TYPES...>`
|
||||
*/
|
||||
template<class TSEQ>
|
||||
concept typeseq = requires (TSEQ seq)
|
||||
{
|
||||
// TSEQ can invoke a function with a Types<TYPES> subclass
|
||||
[]<typename...TYPES>(Types<TYPES...>&){/*placeholder*/}(seq);
|
||||
};
|
||||
|
||||
template<class TSEQ>
|
||||
static constexpr bool is_Typeseq_v = typeseq<TSEQ>;
|
||||
|
||||
}} // namespace lib::meta
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -44,6 +44,44 @@
|
|||
namespace lib {
|
||||
namespace meta {
|
||||
|
||||
template<class T, class TYPES>
|
||||
struct Prepend;
|
||||
|
||||
|
||||
|
||||
/* ========= Specialisations ========= */
|
||||
|
||||
/**
|
||||
* Additional specialisation of the basic type sequence type,
|
||||
* allowing to re-create a (flat) type sequence from a typelist.
|
||||
* @remark recursively prepend-to-tail of the type sequence.
|
||||
*/
|
||||
template<class H, class T>
|
||||
struct Types< Node<H,T> >
|
||||
{
|
||||
using List = Node<H,T>;
|
||||
using Seq = Prepend< H
|
||||
, typename Types<T>::Seq
|
||||
>::Seq;
|
||||
};
|
||||
template<>
|
||||
struct Types<Nil>
|
||||
{
|
||||
using List = Nil;
|
||||
using Seq = Types<>;
|
||||
};
|
||||
template<>
|
||||
struct Types<NilNode>
|
||||
: Types<Nil>
|
||||
{ };
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ========= Information functions ========= */
|
||||
|
||||
/**
|
||||
* Find the index of the first incidence of a type in a type-sequence.
|
||||
|
|
@ -82,6 +120,13 @@ namespace meta {
|
|||
{ };
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ========= Type Sequence Manipulation ========= */
|
||||
|
||||
/**
|
||||
* Helper: prepend a type to an existing type sequence,
|
||||
* thus shifting all elements within the sequence
|
||||
|
|
@ -99,33 +144,6 @@ namespace meta {
|
|||
|
||||
|
||||
|
||||
/**
|
||||
* Additional specialisation of the basic type sequence type,
|
||||
* allowing to re-create a (flat) type sequence from a typelist.
|
||||
* @remark can now be built with the help of \ref Prepend.
|
||||
*/
|
||||
template<class H, class T>
|
||||
struct Types< Node<H,T> >
|
||||
{
|
||||
using List = Node<H,T>;
|
||||
using Seq = Prepend< H
|
||||
, typename Types<T>::Seq
|
||||
>::Seq;
|
||||
};
|
||||
template<>
|
||||
struct Types<Nil>
|
||||
{
|
||||
using List = Nil;
|
||||
using Seq = Types<>;
|
||||
};
|
||||
template<>
|
||||
struct Types<NilNode>
|
||||
: Types<Nil>
|
||||
{ };
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** Helper: separate parts of a type sequence
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@
|
|||
#define LIB_META_VALUE_TYPE_BINDING_H
|
||||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/meta/trait.hpp"
|
||||
|
||||
|
||||
|
|
@ -130,47 +129,5 @@ namespace meta {
|
|||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Decision helper to select between returning results by value or reference.
|
||||
* - when both types can not be reconciled, _no type result_ is provided;
|
||||
* this case can be detected by a compile-time bool-check
|
||||
* - when one of both types is `const`, the `ResType` will be const
|
||||
* - when both types are LValue-references, then the result will be a reference,
|
||||
* otherwise the result will be a value type
|
||||
* @see IterExplorer::expand()
|
||||
*/
|
||||
template<typename T1, typename T2, bool = has_TypeResult<std::common_type<T1,T2>>()>
|
||||
struct CommonResultYield
|
||||
: std::false_type
|
||||
{ };
|
||||
|
||||
template<typename T1, typename T2>
|
||||
struct CommonResultYield<T1, T2, true >
|
||||
: std::true_type
|
||||
{
|
||||
using _Common = std::common_type_t<T1,T2>;
|
||||
// NOTE: unfortunately std::common_type decays (strips cv and reference)
|
||||
static constexpr bool isConst = isConst_v<T1> or isConst_v<T2>;
|
||||
static constexpr bool isRef = isLRef_v<T1> and isLRef_v<T2>;
|
||||
|
||||
using _ConstT = std::conditional_t<isConst
|
||||
, const _Common
|
||||
, _Common
|
||||
>;
|
||||
using _ValRef = std::conditional_t<isRef
|
||||
, std::add_lvalue_reference_t<_ConstT>
|
||||
, std::remove_reference_t<_ConstT>
|
||||
>;
|
||||
|
||||
using ResType = _ValRef;
|
||||
using value_type = RefTraits<ResType>::value_type;
|
||||
using reference = RefTraits<ResType>::reference;
|
||||
using pointer = RefTraits<ResType>::pointer;
|
||||
};
|
||||
|
||||
|
||||
|
||||
}} // namespace lib::meta
|
||||
#endif /*LIB_META_VALUE_TYPE_BINDING_H*/
|
||||
|
|
|
|||
|
|
@ -16,9 +16,14 @@
|
|||
** Metaprogramming with type sequences based on variadic template parameters.
|
||||
** The type rebinding- and helper templates in this header allow to perform
|
||||
** simple sequence manipulations on sequences of template parameters extracted
|
||||
** from variadic parameter packs. The goal is to (pre)process flexible argument
|
||||
** lists _at compile time,_ driven by template instantiation, allowing to specialise
|
||||
** and react specifically on some concrete pattern of argument types.
|
||||
** from variadic parameter packs. Among other things, this allows the sequence
|
||||
** of function arguments to be flexibly reshaped and remoulded at compile-time.
|
||||
** Driven by template instantiation, this enables adaptation in response to
|
||||
** specific argument types for each actual instance. Such metaprogramming
|
||||
** capabilities can be amplified significantly through the use of generic-λ.
|
||||
** @note with C++23, this framework could be expanded to cover also any
|
||||
** »tuple-like« product type, drawing from our non-standard extension
|
||||
** with the `concept tuple_like`, as defined in \ref tuple-concept.hpp.
|
||||
**
|
||||
** @remark in Lumiera, over time three different approaches were developed for
|
||||
** handling sequences of types in metaprogramming; some of these techniques
|
||||
|
|
@ -26,8 +31,10 @@
|
|||
** - templates with variadic arguments (e.g. std::tuple) can be manipulated directly
|
||||
** - a type-sequence `Types<T...>` can be primed / rebound from other variadic templates
|
||||
** - Loki-style type-lists are created from type-sequences and enable elaborate manipulations
|
||||
**
|
||||
** @see feed-manifold.hpp advanced usage example in the Render Engine
|
||||
** @see TupleHelper_test
|
||||
** @see VariadicHelper_test
|
||||
** @see tuple-concept.hpp
|
||||
** @see typelist.hpp
|
||||
** @see function.hpp
|
||||
** @see generator.hpp
|
||||
|
|
@ -42,7 +49,7 @@
|
|||
#include "lib/meta/typelist-util.hpp"
|
||||
#include "lib/meta/typeseq-util.hpp"
|
||||
#include "lib/meta/variadic-rebind.hpp"
|
||||
#include "lib/meta/util.hpp"
|
||||
#include "lib/meta/trait.hpp"
|
||||
|
||||
namespace lib {
|
||||
namespace meta {
|
||||
|
|
@ -160,7 +167,7 @@ namespace meta {
|
|||
* - the nested template #Apply wraps each type into another template
|
||||
* - #Rebind likewise instantiates another template with the element types
|
||||
* - #AndAll applies a predicate and combines the result with _logical and_
|
||||
* - #OrAll similarly evaluates _logical or_ on the application results
|
||||
* - #OrAny similarly evaluates _logical or_ on the application results
|
||||
*/
|
||||
template<class X, typename =void>
|
||||
struct ElmTypes
|
||||
|
|
@ -177,7 +184,7 @@ namespace meta {
|
|||
template<template<class> class PRED>
|
||||
using AndAll = std::__and_<PRED<X>>;
|
||||
template<template<class> class PRED>
|
||||
using OrAll = std::__or_<PRED<X>>;
|
||||
using OrAny = std::__or_<PRED<X>>;
|
||||
};
|
||||
|
||||
/** Partial specialisation to handle type sequences */
|
||||
|
|
@ -199,10 +206,23 @@ namespace meta {
|
|||
using AndAll = ElmTypes<Apply<PRED>>::template Rebind<std::__and_>;
|
||||
|
||||
template<template<class> class PRED>
|
||||
using OrAll = ElmTypes<Apply<PRED>>::template Rebind<std::__or_>;
|
||||
using OrAny = ElmTypes<Apply<PRED>>::template Rebind<std::__or_>;
|
||||
};
|
||||
|
||||
// Note: a further specialisation for any »tuple-like« is defined in tuple-helper.hpp
|
||||
// Note: a further specialisation for any »tuple-like« is defined in tuple-concept.hpp
|
||||
|
||||
|
||||
|
||||
template<class SEQ>
|
||||
using Any_Const = ElmTypes<SEQ>::template OrAny<isConst>;
|
||||
template<class SEQ>
|
||||
using All_LRef = ElmTypes<SEQ>::template AndAll<isLRef>;
|
||||
|
||||
template<class SEQ>
|
||||
static constexpr bool Any_Const_v = Any_Const<SEQ>::value;
|
||||
template<class SEQ>
|
||||
static constexpr bool All_LRef_v = All_LRef<SEQ>::value;
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,16 @@
|
|||
** Implementation of the concrete (sub)-closure of a command, responsible for
|
||||
** invoking the actual command operation with the concrete (binding) arguments.
|
||||
**
|
||||
** @todo 4/2026 this header seems to be more of a concept draft from the early days.
|
||||
** At that time, the C++ language did not have much function tools, did not
|
||||
** support lambdas and tuples, and thus invoking a functor object and passing
|
||||
** an arbitrary sequence of stored arguments was an innovative concept to be
|
||||
** implemented from scratch. And during that development process, some ideas
|
||||
** regarding the specific handling of relevant argument types was intermingled
|
||||
** with the creation of generic general purpose tooling. However, with the
|
||||
** switch to C++23, in summer 2025, most of that old implementation was
|
||||
** removed and replaced by standard library facilities. The #activated_
|
||||
** flag seems to be the only piece of actual functionality here.
|
||||
** @see Command
|
||||
** @see command-closure.hpp
|
||||
** @see command-storage-holder.hpp
|
||||
|
|
@ -28,109 +38,35 @@
|
|||
#define CONTROL_COMMAND_OP_CLOSURE_H
|
||||
|
||||
#include "lib/meta/function.hpp"
|
||||
#include "lib/meta/function-closure.hpp"
|
||||
#include "lib/meta/tuple-accessor.hpp"
|
||||
#include "lib/meta/tuple-record-init.hpp"
|
||||
#include "steam/control/command-closure.hpp"
|
||||
#include "steam/control/argument-erasure.hpp"
|
||||
#include "lib/typed-allocation-manager.hpp"
|
||||
#include "lib/format-cout.hpp"
|
||||
#include "lib/meta/tuple-helper.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
|
||||
|
||||
|
||||
namespace steam {
|
||||
namespace control {
|
||||
|
||||
using lib::meta::_Fun;
|
||||
using lib::meta::Tuple;
|
||||
using lib::meta::BuildTupleAccessor;
|
||||
using lib::meta::buildTuple;
|
||||
using lib::meta::Nil;
|
||||
|
||||
using lib::TypedAllocationManager;
|
||||
using std::function;
|
||||
using std::ostream;
|
||||
using std::string;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** Helper for accessing an individual function parameter */
|
||||
template
|
||||
< typename TY
|
||||
, class BASE
|
||||
, class TUP
|
||||
, uint idx
|
||||
>
|
||||
class ParamAccessor
|
||||
: public BASE
|
||||
{
|
||||
TY & element() { return std::get<idx> (*this); }
|
||||
TY const& element() const { return std::get<idx> (*this); }
|
||||
|
||||
public:
|
||||
using BASE::BASE;
|
||||
|
||||
|
||||
////////////////////TODO the real access operations (e.g. for serialising) go here
|
||||
|
||||
/////////////////////////////////////////////////////////////TICKET #798 : we need to pick up arguments from a lib::diff::Record.
|
||||
|
||||
ostream&
|
||||
dump (ostream& output) const
|
||||
{
|
||||
return BASE::dump (output << element() << ',');
|
||||
}
|
||||
};
|
||||
|
||||
template<class TUP, uint n>
|
||||
class ParamAccessor<Nil, TUP, TUP, n> ///< used for recursion end of implementation functions
|
||||
: public TUP
|
||||
{
|
||||
public:
|
||||
ParamAccessor (TUP const& tup)
|
||||
: TUP(tup)
|
||||
{ }
|
||||
|
||||
/** we deliberately support immutable types as command arguments */
|
||||
ParamAccessor& operator= (ParamAccessor const&) =delete;
|
||||
ParamAccessor& operator= (ParamAccessor&&) =delete;
|
||||
ParamAccessor (ParamAccessor const&) =default;
|
||||
ParamAccessor (ParamAccessor&&) =default;
|
||||
|
||||
|
||||
////////////////////TODO the recursion-end of the access operations goes here
|
||||
|
||||
ostream&
|
||||
dump (ostream& output) const
|
||||
{
|
||||
return output;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* closure to deal with the actual command operation.
|
||||
* This includes holding the invocation parameter tuple
|
||||
*/
|
||||
template<typename SIG>
|
||||
class OpClosure
|
||||
: util::NonAssign
|
||||
{
|
||||
using Args = _Fun<SIG>::Args;
|
||||
using Builder = BuildTupleAccessor<ParamAccessor, Args>;
|
||||
using Args = _Fun<SIG>::Args;
|
||||
|
||||
using ParamStorageTuple = Builder::Product;
|
||||
|
||||
ParamStorageTuple params_;
|
||||
Tuple<Args> params_;
|
||||
bool activated_;
|
||||
|
||||
public:
|
||||
|
|
@ -138,8 +74,8 @@ namespace control {
|
|||
|
||||
|
||||
OpClosure()
|
||||
: params_(Tuple<Args>())
|
||||
, activated_(false)
|
||||
: params_{}
|
||||
, activated_{false}
|
||||
{ }
|
||||
|
||||
explicit
|
||||
|
|
@ -148,13 +84,6 @@ namespace control {
|
|||
, activated_(true)
|
||||
{ }
|
||||
|
||||
/** we deliberately support immutable types as command arguments */
|
||||
OpClosure& operator= (OpClosure const&) =delete;
|
||||
OpClosure& operator= (OpClosure&&) =delete;
|
||||
OpClosure (OpClosure const&) =default;
|
||||
OpClosure (OpClosure&&) =default;
|
||||
|
||||
|
||||
bool
|
||||
isValid () const
|
||||
{
|
||||
|
|
@ -165,36 +94,25 @@ namespace control {
|
|||
/** Core operation: use the embedded argument tuple for invoking a functor
|
||||
* @param unboundFunctor an function object, whose function arguments are
|
||||
* required to match the types of the embedded ParamStorageTuple
|
||||
* @note ASSERTION failure if the function signature
|
||||
* doesn't match the argument types tuple.
|
||||
* @note the functor might actually \em modify the param values.
|
||||
* @note the functor might actually _modify the param values._
|
||||
* Thus this function can't be const.
|
||||
*/
|
||||
void
|
||||
invoke (CmdFunctor const& unboundFunctor)
|
||||
{
|
||||
ArgTuple& paramTuple{params_};
|
||||
std::apply (unboundFunctor.getFun<SIG>(), paramTuple);
|
||||
std::apply (unboundFunctor.getFun<SIG>(), params_);
|
||||
}
|
||||
|
||||
|
||||
|
||||
operator string() const
|
||||
{
|
||||
std::ostringstream buff;
|
||||
params_.dump (buff << "OpClosure(" );
|
||||
|
||||
string dumped (buff.str());
|
||||
if (10 < dumped.length())
|
||||
// remove trailing comma...
|
||||
return dumped.substr (0, dumped.length()-1) +")";
|
||||
else
|
||||
return dumped+")";
|
||||
buff << "OpClosure";
|
||||
lib::meta::joinTupleParen (params_, buff);
|
||||
return buff.str();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
}} // namespace steam::control
|
||||
#endif /*CONTROL_COMMAND_OP_CLOSURE_H*/
|
||||
|
|
|
|||
|
|
@ -38,7 +38,10 @@
|
|||
#define CONTROL_COMMAND_SIMPLE_CLOSURE_H
|
||||
|
||||
#include "lib/typed-allocation-manager.hpp"
|
||||
#include "steam/control/command-closure.hpp"
|
||||
#include "steam/control/argument-erasure.hpp"
|
||||
#include "steam/control/command-op-closure.hpp"
|
||||
#include "lib/meta/tuple-record-init.hpp"
|
||||
#include "lib/opaque-holder.hpp"
|
||||
|
||||
#include <string>
|
||||
|
|
@ -50,6 +53,7 @@ namespace control {
|
|||
namespace err = lumiera::error;
|
||||
|
||||
using lib::InPlaceBuffer;
|
||||
using lib::meta::buildTuple;
|
||||
using std::string;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -36,9 +36,12 @@
|
|||
#define CONTROL_COMMAND_STORAGE_HOLDER_H
|
||||
|
||||
#include "lib/typed-allocation-manager.hpp"
|
||||
#include "steam/control/command-closure.hpp"
|
||||
#include "steam/control/argument-erasure.hpp"
|
||||
#include "steam/control/command-op-closure.hpp"
|
||||
#include "steam/control/memento-tie.hpp"
|
||||
#include "steam/control/command-impl-clone-builder.hpp"
|
||||
#include "lib/meta/tuple-record-init.hpp"
|
||||
#include "lib/opaque-holder.hpp"
|
||||
|
||||
#include <string>
|
||||
|
|
@ -50,6 +53,7 @@ namespace control {
|
|||
namespace err = lumiera::error;
|
||||
|
||||
using lib::InPlaceBuffer;
|
||||
using lib::meta::buildTuple;
|
||||
using std::string;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@
|
|||
namespace test{
|
||||
namespace error = lumiera::error;
|
||||
|
||||
using util::stringify;
|
||||
using util::contains;
|
||||
using util::isnil;
|
||||
using lib::Symbol;
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
** reference kind (value, lvalue, rvalue) of a template parameter instantiation.
|
||||
** For GNU compatible compilers, we expose also the interface to the internal
|
||||
** ABI for [demangling type names](\ref demangleCxx).
|
||||
**
|
||||
** @note this header is included into a large number of tests.
|
||||
** @remark util::showType also uses this ABI and indicates also reference / pointer
|
||||
** @warning this header is included into a large number of tests.
|
||||
** @see TestHelper_test
|
||||
** @see TestHelperDemangling_test
|
||||
**
|
||||
|
|
@ -58,6 +58,8 @@ namespace test{
|
|||
using lib::meta::disable_if;
|
||||
using lib::meta::demangleCxx;
|
||||
using lib::meta::humanReadableTypeID;
|
||||
using util::showType;
|
||||
using util::showTypes;
|
||||
|
||||
|
||||
constexpr auto ROUGH_PRECISION = pow (10, -3);
|
||||
|
|
@ -168,116 +170,6 @@ namespace test{
|
|||
|
||||
|
||||
|
||||
|
||||
namespace { // helper for printing type diagnostics
|
||||
|
||||
template<typename X>
|
||||
struct TypeDiagnostics
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = "";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<const X>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "const ";
|
||||
static constexpr auto postfix = "";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X&>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = "&";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X&&>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = " &&";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X const&>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = " const&";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X const&&>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "const ";
|
||||
static constexpr auto postfix = " &&";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X *>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = " *";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<const X *>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "const ";
|
||||
static constexpr auto postfix = " *";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<const X * const>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "const ";
|
||||
static constexpr auto postfix = " * const";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X * const>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = " * const";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X * const *>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = " * const *";
|
||||
};
|
||||
}
|
||||
|
||||
/** diagnostic type output, including const and similar adornments
|
||||
* @warning operates after-the-fact and relies on mangled type names
|
||||
* plus several heuristics. Output might thus not be entirely correct,
|
||||
* especially when several levels of const, pointer and references are involved.
|
||||
* If in doubt, place the TypeDebugger<T> to reveal the type as the compiler sees it.
|
||||
* @remarks the function lib::meta::typeStr removes adornments and does not work on all kinds
|
||||
* of reference. This helper attempts to work around those limitations.
|
||||
*/
|
||||
template<typename X>
|
||||
inline string
|
||||
showType()
|
||||
{
|
||||
using Case = TypeDiagnostics<X>;
|
||||
using Type = Case::Type;
|
||||
|
||||
return Case::prefix
|
||||
+ humanReadableTypeID (typeid(Type).name())
|
||||
+ Case::postfix;
|
||||
}
|
||||
|
||||
template<typename...TS>
|
||||
string
|
||||
showTypes()
|
||||
{
|
||||
return "<| " + ((showType<TS>()+", ") + ... + "|>");
|
||||
}
|
||||
|
||||
|
||||
/** helper for investigating a variadic argument pack
|
||||
* @remark you can pass arguments directly to this function,
|
||||
* but be sure to use `std::forward<ARGS>(args)...` when forwarding
|
||||
|
|
|
|||
|
|
@ -87,6 +87,11 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
TEST "Common reconciled return types" CommonResult_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
TEST "Duck typing support" DuckDetector_test <<END
|
||||
out: HasNested_Core<PropperGander> : Yes
|
||||
out: HasNested_Core<Propaganda> : No
|
||||
|
|
@ -133,6 +138,11 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
TEST "generate functor trampoline" FunTrampoline_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
TEST "member function late binding" LateBindInstance_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
|
@ -294,6 +304,11 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
TEST "Runtime-indexed tuple access" TupleIdxAdaptor_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
TEST "Helper to show demangled C++ names" TypeDemangling_test <<END
|
||||
out: .+lib.meta.test.Space.+Outer.+Inner
|
||||
out-lit: lib::meta::test::Space const* (*)(lib::meta::test::Outer<lib::meta::test::Space>::Inner const&&)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include "lib/util.hpp"
|
||||
|
||||
#include "lib/iter-adapter.hpp"
|
||||
#include "lib/meta/common-result.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
|
@ -228,61 +229,26 @@ namespace test{
|
|||
}
|
||||
|
||||
|
||||
/** @test construction of a common result type.
|
||||
* - based on `std::common_type`
|
||||
* - so there must be some common ground
|
||||
* - if any of the types is by-value, the result is
|
||||
* - if any of the types is const, the result is const
|
||||
/** @test when combining functors, a common result type can be detected.
|
||||
* @see CommonResult_test
|
||||
* @see lib/meta/common-result.hpp
|
||||
*/
|
||||
void
|
||||
verify_TypeReconciliation()
|
||||
{
|
||||
using C1 = Common<int,string>;
|
||||
CHECK (not C1());
|
||||
using C1 = meta::CommonResult<int,string>;
|
||||
CHECK (not C1{});
|
||||
CHECK (not C1::value);
|
||||
|
||||
using C2 = Common<int,long*>;
|
||||
CHECK (not C2()); // can not be reconciled
|
||||
CHECK (not C2::value);
|
||||
// using X = C2::ResType; // does not (and should not) compile
|
||||
using C2 = Common<string&,string>;
|
||||
CHECK (showType<C2::Type>() == "string"_expect );
|
||||
|
||||
using C3 = Common<string,string>;
|
||||
CHECK (C3());
|
||||
CHECK (showType<C3::ResType>() == "string"_expect );
|
||||
|
||||
using C4 = Common<string&,string>;
|
||||
CHECK (showType<C4::ResType>() == "string"_expect );
|
||||
|
||||
using C5 = Common<string&,string&>;
|
||||
CHECK (showType<C5::ResType>() == "string&"_expect ); // ref access to both is possible
|
||||
|
||||
using C6 = Common<string&,string&&>;
|
||||
CHECK (showType<C6::ResType>() == "string"_expect ); // caution, RValue might be a temporary
|
||||
|
||||
using C7 = Common<string&&,string&&>;
|
||||
CHECK (showType<C7::ResType>() == "string"_expect );
|
||||
|
||||
using C8 = Common<string const&, string const&>;
|
||||
CHECK (showType<C8::ResType>() == "string const&"_expect );
|
||||
|
||||
using C9 = Common<string const&, string&>;
|
||||
CHECK (showType<C9::ResType>() == "string const&"_expect ); // reconcile to const&
|
||||
|
||||
using C10 = Common<string const&, string>;
|
||||
CHECK (showType<C10::ResType>() == "const string"_expect );
|
||||
|
||||
using C11 = Common<string&&, string const&>;
|
||||
CHECK (showType<C11::ResType>() == "const string"_expect ); // reconciled to value type
|
||||
|
||||
using C12 = Common<long const&, int>;
|
||||
CHECK (showType<C12::ResType>() == "const long"_expect ); // reconciled to the larger number type
|
||||
|
||||
using C13 = Common<double&, long const&>;
|
||||
CHECK (showType<C13::ResType>() == "double const&"_expect ); // usual in C++ (loss of precision possible)
|
||||
using C3 = Common<double&, long const&>;
|
||||
CHECK (showType<C3::Type>() == "double"_expect ); // usual in C++ (loss of precision possible)
|
||||
}
|
||||
|
||||
template<typename T1, typename T2>
|
||||
using Common = meta::CommonResultYield<T1,T2>;
|
||||
using Common = meta::CommonResult<T1,T2>;
|
||||
};
|
||||
|
||||
LAUNCHER (IterCoreAdapter_test, "unit common");
|
||||
|
|
|
|||
224
tests/lib/meta/common-result-test.cpp
Normal file
224
tests/lib/meta/common-result-test.cpp
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
CommonResult(Test) - verify reconciled function return types
|
||||
|
||||
Copyright (C)
|
||||
2026, 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 common-result-test.cpp
|
||||
** unit test \ref CommonResult_test
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#include "test/run.hpp"
|
||||
#include "test/test-helper.hpp"
|
||||
#include "lib/meta/tuple-helper.hpp"
|
||||
#include "lib/random.hpp"
|
||||
|
||||
#include "lib/meta/common-result.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
using test::showType;
|
||||
using std::string;
|
||||
using std::tuple;
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta {
|
||||
namespace test {
|
||||
|
||||
namespace { // Dummy hierarchy
|
||||
|
||||
struct II { };
|
||||
struct S1 : II { };
|
||||
struct S2 : II { };
|
||||
|
||||
template<typename X>
|
||||
X yield();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************//**
|
||||
* @test demonstrate the metafunction to determine a common return type.
|
||||
* @see TupleIdxAdaptor_test
|
||||
* @see IterExplorer_test::verify_expandOperation
|
||||
* @see common-result.hpp
|
||||
* @see iter-explorer.hpp
|
||||
*/
|
||||
class CommonResult_test : public Test
|
||||
{
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
simpleUsage();
|
||||
underlyingMechanism();
|
||||
demonstrate_TypeReconciliation();
|
||||
demonstrate_Metaprocessing();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** @test how to use the metafunction and the concept */
|
||||
void
|
||||
simpleUsage()
|
||||
{
|
||||
using CT = CommonResult_t<int,char,long,short>;
|
||||
CHECK (showType<CT>() == "long"_expect );
|
||||
|
||||
CHECK ((not has_CommonResult_v<int,string>));
|
||||
CHECK ((not reconcilable<int,string>)); // Concept
|
||||
|
||||
CHECK ((reconcilable_ref<short&, short const&>)); // Concept
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** @test show the language mechanism used as foundation */
|
||||
void
|
||||
underlyingMechanism()
|
||||
{
|
||||
auto expr = defaultGen.i(2)? defaultGen.u64() : defaultGen.uni();
|
||||
CHECK (expr >= 0);
|
||||
CHECK (showType<decltype(expr)>() == "double"_expect );
|
||||
|
||||
CHECK (showType<decltype( true? short(1) : long(1))>() == "long"_expect );
|
||||
CHECK (showType<decltype(false? short(1) : long(1))>() == "long"_expect );
|
||||
|
||||
CHECK (showType<decltype( true? yield<const string>() : yield<string&>())>() == "const string"_expect );
|
||||
|
||||
CHECK (showType<decltype( true? yield<II> () : yield<S1>())>() == "II"_expect );
|
||||
CHECK (showType<decltype( true? yield<II&>() : yield<S1>())>() == "II"_expect );
|
||||
}
|
||||
|
||||
|
||||
/** @test show how various combinations of types are handled
|
||||
* - some types can not be reconciled
|
||||
* - there is no common type for an empty sequence of types
|
||||
* - result falls back to return by-value (or RRef),
|
||||
* unless all involved types are given by LRef
|
||||
* - types that must be converted are reconciled into a value
|
||||
* - C++ allows to promote integral numbers to floating-point
|
||||
* - superclass / subclass relationship is observed
|
||||
* - yet no automatic upcast to superclass is performed
|
||||
*/
|
||||
void
|
||||
demonstrate_TypeReconciliation()
|
||||
{
|
||||
CHECK ((not CommonResult<int,long*>{})); // can not be reconciled
|
||||
CHECK ((not CommonResult<int,long*>::value));
|
||||
// using X = CommonResult<int,long*>::Type; // does not (and should not) compile
|
||||
// using X = CommonResult_t<int,long*>; // does not compile
|
||||
CHECK ((not has_CommonResult_v<int,long*>));
|
||||
CHECK ((not reconcilable<int,long*>)); // a concept
|
||||
|
||||
CHECK ((not CommonResult<>::value)); // ¬ ∃ T∈{} |…
|
||||
// using X = CommonResult_t<>; // does not compile
|
||||
|
||||
CHECK ((showType<CommonResult_t<string>>()) == "string &&"_expect );
|
||||
CHECK ((showType<CommonResult_t<const string>>()) == "const string &&"_expect );
|
||||
CHECK ((showType<CommonResult_t<volatile string>>()) == "string &&"_expect );
|
||||
CHECK ((showType<CommonResult_t<const volatile string>>()) == "const string &&"_expect );
|
||||
CHECK ((showType<CommonResult_t<string & >>()) == "string&"_expect );
|
||||
CHECK ((showType<CommonResult_t<string &&>>()) == "string &&"_expect );
|
||||
CHECK ((showType<CommonResult_t<string const& >>()) == "string const&"_expect );
|
||||
CHECK ((showType<CommonResult_t<string * >>()) == "string* &&"_expect );
|
||||
CHECK ((showType<CommonResult_t<string *& >>()) == "string*&"_expect );
|
||||
CHECK ((showType<CommonResult_t<string const *& >>()) == "string const*&"_expect );
|
||||
CHECK ((showType<CommonResult_t<string * const& >>()) == "string* const&"_expect );
|
||||
CHECK ((showType<CommonResult_t<string * const >>()) == "const string* &&"_expect );
|
||||
|
||||
CHECK ((showType<CommonResult_t<string,string>>()) == "string &&"_expect );
|
||||
CHECK ((showType<CommonResult_t<string,string&>>()) == "string"_expect );
|
||||
CHECK ((showType<CommonResult_t<string,string&,string>>()) == "string &&"_expect );
|
||||
CHECK ((showType<CommonResult_t<string,string,string&>>()) == "string"_expect );
|
||||
CHECK ((showType<CommonResult_t<const string, string&>>()) == "const string"_expect );
|
||||
CHECK ((showType<CommonResult_t<string&, string&&>>()) == "string"_expect );
|
||||
CHECK ((showType<CommonResult_t<string&&, string&&>>()) == "string &&"_expect );
|
||||
CHECK ((showType<CommonResult_t<string&&,const string&&>>()) == "const string &&"_expect );
|
||||
|
||||
CHECK ((showType<CommonResult_t<float*, float* const>>()) == "const float* &&"_expect );
|
||||
CHECK ((showType<CommonResult_t<float*, float const*>>()) == "const float *"_expect );
|
||||
CHECK ((showType<CommonResult_t<float*, void* >>()) == "void *"_expect );
|
||||
CHECK ((showType<CommonResult_t<float* &, void* &>>()) == "void *"_expect );
|
||||
CHECK ((showType<CommonResult_t<float* &, void* &>>()) == "void *"_expect );
|
||||
CHECK ((showType<CommonResult_t<short&, long&>>()) == "long"_expect );
|
||||
CHECK ((showType<CommonResult_t<const int&,uint16_t,int64_t>>()) == "long"_expect );
|
||||
|
||||
CHECK ((showType<CommonResult_t<float,double>>()) == "double"_expect );
|
||||
CHECK ((showType<CommonResult_t<long, double>>()) == "double"_expect );
|
||||
CHECK ((showType<CommonResult_t<long, double, float>>()) == "double"_expect );
|
||||
CHECK ((showType<CommonResult_t<long const&,double const&>>()) == "double"_expect );
|
||||
|
||||
CHECK ((not reconcilable<float,float*> ));
|
||||
CHECK ((not reconcilable<float*,float**> ));
|
||||
|
||||
CHECK (( reconcilable<short,long> ));
|
||||
CHECK (( reconcilable<short,long&> ));
|
||||
CHECK ((not reconcilable_ref<short,long> ));
|
||||
CHECK ((not reconcilable_ref<short,long&> ));
|
||||
CHECK (( reconcilable_ref<long&,long&> ));
|
||||
CHECK ((not reconcilable_ref<short&,long&> ));
|
||||
|
||||
// II ◁— S1 ∧ II ◁— S2
|
||||
CHECK (( reconcilable<S1,II> ));
|
||||
CHECK (( reconcilable<II,S1> ));
|
||||
CHECK (( reconcilable<II,S2> ));
|
||||
CHECK ((not reconcilable<S1,S2> ));
|
||||
CHECK ((not reconcilable<S1,S2,II> )); // NOTE: this is surprising
|
||||
CHECK (( reconcilable<II,S1,S2> )); // the reason is processing from left-to-right
|
||||
CHECK (( reconcilable<II&,S1&,S2&> ));
|
||||
|
||||
CHECK ((showType<CommonResult_t<II,S1>>()) == "II &&"_expect );
|
||||
CHECK ((showType<CommonResult_t<S1,II>>()) == "II &&"_expect );
|
||||
CHECK ((showType<CommonResult_t<S1,II,S2>>()) == "II &&"_expect );
|
||||
CHECK ((showType<CommonResult_t<S1&,II,S2>>()) == "II &&"_expect );
|
||||
CHECK ((showType<CommonResult_t<II&,S1&,S2&>>()) == "II&"_expect );
|
||||
}
|
||||
|
||||
|
||||
/** @test show use in Metaprogramming with variadics */
|
||||
void
|
||||
demonstrate_Metaprocessing()
|
||||
{
|
||||
auto tup = std::make_tuple (1, 2.3, '4');
|
||||
CHECK (tup == "«tuple<int, double, char>»──(1,2.3,4)"_expect );
|
||||
|
||||
using Tup = decltype(tup);
|
||||
CHECK (showType<Tup>() == "tuple<int, double, char>"_expect );
|
||||
|
||||
using ValSeq = ElmTypes<Tup>::Seq;
|
||||
using ConstSeq = ElmTypes<ValSeq>::template Apply<std::add_lvalue_reference_t>;
|
||||
CHECK (showType<ConstSeq>() == "Types<int&, double&, char&>"_expect );
|
||||
|
||||
using CommRes = CommonResult_t<ConstSeq>;
|
||||
CHECK (showType<CommRes>() == "double"_expect );
|
||||
|
||||
// Pick function overload based on reconcilability
|
||||
auto trp = std::tuple_cat (tup, tuple("😈"));
|
||||
CHECK (doIt (tup) == "Yes we can"_expect );
|
||||
CHECK (doIt (trp) == "SO SAD!↯↯!"_expect );
|
||||
}
|
||||
|
||||
template<typename...TYPES>
|
||||
requires reconcilable<TYPES...>
|
||||
static string doIt (tuple<TYPES...>) { return "Yes we can"; }
|
||||
|
||||
template<typename...TYPES>
|
||||
static string doIt (tuple<TYPES...>) { return "SO SAD!↯↯!"; }
|
||||
};
|
||||
|
||||
LAUNCHER (CommonResult_test, "unit common");
|
||||
|
||||
|
||||
}}} // namespace lib::meta::test
|
||||
|
||||
113
tests/lib/meta/fun-trampoline-test.cpp
Normal file
113
tests/lib/meta/fun-trampoline-test.cpp
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
FunTrampoline(Test) - demonstrate building a static dispatcher table
|
||||
|
||||
Copyright (C)
|
||||
2026, 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 fun-trampoline-test.cpp
|
||||
** unit test \ref FunTrampoline_test
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#include "test/run.hpp"
|
||||
#include "test/test-helper.hpp"
|
||||
#include "lib/meta/tuple-helper.hpp"
|
||||
#include "lib/format-string.hpp"
|
||||
#include "lib/format-obj.hpp"
|
||||
|
||||
#include "lib/meta/fun-trampoline.hpp"
|
||||
|
||||
|
||||
using test::showType;
|
||||
using std::operator""s;
|
||||
using std::string;
|
||||
using util::_Fmt;
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta {
|
||||
namespace test {
|
||||
|
||||
namespace { // Builder to populate the trampoline table
|
||||
|
||||
template<typeseq SEQ>
|
||||
struct Stringify
|
||||
{
|
||||
using Tuple = ElmTypes<SEQ>::Tup;
|
||||
using Functor = string(*)(Tuple const&);
|
||||
|
||||
template<size_t idx>
|
||||
constexpr auto
|
||||
build()
|
||||
{
|
||||
using ElmT = std::tuple_element_t<idx, Tuple>;
|
||||
|
||||
return [](Tuple const& tuple) -> string
|
||||
{
|
||||
static _Fmt render{"%02d: «%s»%|20t||%s"};
|
||||
return {render % idx
|
||||
% util::showType<ElmT>()
|
||||
% util::toString<ElmT> (getElm<idx> (tuple))};
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*************************************************************//**
|
||||
* @test demonstrate the setup of a static functor trampoline.
|
||||
* @see TupleIdxAdaptor_test
|
||||
*/
|
||||
class FunTrampoline_test : public Test
|
||||
{
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
demonstrate_TupleAccessor();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** @test build a trampoline table to access and handle tuple fields
|
||||
* - [setup a builder](\ref Stringify) to populate the table.
|
||||
* - use a typedef to provide the actual tuple type
|
||||
* - without further ado, the trampoline is ready
|
||||
*/
|
||||
void
|
||||
demonstrate_TupleAccessor()
|
||||
{
|
||||
auto tup = std::make_tuple (1, 2.3, '4', "⁵₅⁵", "😈"s);
|
||||
|
||||
using Tramp = TupStringer<decltype(tup)>;
|
||||
CHECK (Tramp::size() == 5);
|
||||
CHECK (Tramp::memSiz() == 5 * sizeof(void*));
|
||||
|
||||
CHECK (showType<Tramp::ResType>() == "string"_expect);
|
||||
|
||||
CHECK (Tramp::dispatch(0)(tup) == "00: «int» |1"_expect );
|
||||
CHECK (Tramp::dispatch(1)(tup) == "01: «double» |2.3"_expect);
|
||||
CHECK (Tramp::dispatch(2)(tup) == "02: «char» |4"_expect );
|
||||
CHECK (Tramp::dispatch(3)(tup) == "03: «const char *»|⁵₅⁵"_expect);
|
||||
CHECK (Tramp::dispatch(4)(tup) == "04: «string» |😈"_expect );
|
||||
|
||||
getElm<3>(tup) = "🚀💣🔥";
|
||||
CHECK (Tramp::dispatch(3)(tup) == "03: «const char *»|🚀💣🔥"_expect);
|
||||
}
|
||||
|
||||
template<tup Tup>
|
||||
using TupStringer = FunTrampoline<Stringify, typename ElmTypes<Tup>::Seq>;
|
||||
};
|
||||
|
||||
LAUNCHER (FunTrampoline_test, "unit common");
|
||||
|
||||
|
||||
}}} // namespace lib::meta::test
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
#include "test/run.hpp"
|
||||
#include "test/test-helper.hpp"
|
||||
#include "test/tracking-dummy.hpp"
|
||||
#include "lib/meta/tuple-accessor.hpp"
|
||||
#include "lib/meta/tuple-helper.hpp"
|
||||
#include "lib/meta/function.hpp"
|
||||
#include "lib/format-cout.hpp"
|
||||
#include "lib/format-util.hpp"
|
||||
|
|
@ -36,7 +36,7 @@ namespace test {
|
|||
using std::move;
|
||||
using std::tuple;
|
||||
using std::string;
|
||||
using lib::meta::dump;
|
||||
using lib::meta::joinTupleParen;
|
||||
using ::test::Tracker;
|
||||
|
||||
|
||||
|
|
@ -160,19 +160,19 @@ namespace test {
|
|||
{
|
||||
long dummy{555};
|
||||
InstancePlaceholder<long> marker;
|
||||
CHECK (dump(lateBindInstance (dummy, tuple<>{})) == "()"_expect);
|
||||
CHECK (dump(lateBindInstance (dummy, tuple{42})) == "(42)"_expect);
|
||||
CHECK (dump(lateBindInstance (dummy, tuple{1,2,3})) == "(1,2,3)"_expect);
|
||||
CHECK (dump(lateBindInstance (dummy, tuple{marker,2,3})) == "(↗555,2,3)"_expect);
|
||||
CHECK (dump(lateBindInstance (dummy, tuple{1,marker,3})) == "(1,↗555,3)"_expect);
|
||||
CHECK (dump(lateBindInstance (dummy, tuple{1,2,marker})) == "(1,2,↗555)"_expect);
|
||||
CHECK (dump(lateBindInstance (dummy, tuple{marker})) == "(↗555)"_expect);
|
||||
CHECK (dump(lateBindInstance (dummy, tuple{string{"1"}
|
||||
,"2"
|
||||
,marker
|
||||
,tuple(2,3)
|
||||
, 5.5
|
||||
})) == "(1,2,↗555,«tuple<int, int>»──(2,3),5.5)"_expect);
|
||||
CHECK (joinTupleParen(lateBindInstance (dummy, tuple<>{})) == "()"_expect);
|
||||
CHECK (joinTupleParen(lateBindInstance (dummy, tuple{42})) == "(42)"_expect);
|
||||
CHECK (joinTupleParen(lateBindInstance (dummy, tuple{1,2,3})) == "(1,2,3)"_expect);
|
||||
CHECK (joinTupleParen(lateBindInstance (dummy, tuple{marker,2,3})) == "(↗555,2,3)"_expect);
|
||||
CHECK (joinTupleParen(lateBindInstance (dummy, tuple{1,marker,3})) == "(1,↗555,3)"_expect);
|
||||
CHECK (joinTupleParen(lateBindInstance (dummy, tuple{1,2,marker})) == "(1,2,↗555)"_expect);
|
||||
CHECK (joinTupleParen(lateBindInstance (dummy, tuple{marker})) == "(↗555)"_expect);
|
||||
CHECK (joinTupleParen(lateBindInstance (dummy, tuple{string{"1"}
|
||||
,"2"
|
||||
,marker
|
||||
,tuple(2,3)
|
||||
, 5.5
|
||||
})) == "(1,2,↗555,«tuple<int, int>»──(2,3),5.5)"_expect);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ using test::showSizeof;
|
|||
using test::showType;
|
||||
using util::toString;
|
||||
using util::_Fmt;
|
||||
using std::operator""s;
|
||||
using std::is_same_v;
|
||||
using std::make_tuple;
|
||||
using std::get;
|
||||
|
|
@ -78,6 +79,7 @@ namespace test {
|
|||
run (Arg)
|
||||
{
|
||||
check_diagnostics();
|
||||
check_string_conversion();
|
||||
check_tuple_from_Typelist();
|
||||
demonstrate_generic_iteration();
|
||||
verify_tuple_like_concept();
|
||||
|
|
@ -113,6 +115,21 @@ namespace test {
|
|||
}
|
||||
|
||||
|
||||
/** @test apply generic string conversion, implemented by a fold-expression
|
||||
*/
|
||||
void
|
||||
check_string_conversion()
|
||||
{
|
||||
auto tup = make_tuple (1, 2.3, '4', "⁵₅⁵", "😈"s);
|
||||
|
||||
CHECK (showType<decltype(tup)>() == "tuple<int, double, char, char const*, string>"_expect);
|
||||
|
||||
CHECK (joinTuple (tup,"•") == "1•2.3•4•⁵₅⁵•😈"_expect );
|
||||
CHECK (joinTupleParen (tup) == "(1,2.3,4,⁵₅⁵,😈)"_expect );
|
||||
CHECK (toString (tup) == "«tuple<int, double, char, char const*, string>»──(1,2.3,4,⁵₅⁵,😈)"_expect );
|
||||
}
|
||||
|
||||
|
||||
/** @test verify the ability to generate tuple types from typelist metaprogramming
|
||||
* - the resulting types are plain flat `std::tuple` instantiations
|
||||
* - memory layout is identical to a POD, as expected
|
||||
|
|
@ -227,7 +244,7 @@ namespace test {
|
|||
// Note however, the same can also be achieved simpler,
|
||||
// using any meta-function which takes a single type argument...
|
||||
CHECK (not ElmTypes<Tup>::AndAll<std::is_integral>()); // ... __and_<is_integral<int>,is_integral<double>,is_integral<char>>
|
||||
CHECK ( ElmTypes<Tup>::OrAll<std::is_integral>());
|
||||
CHECK ( ElmTypes<Tup>::OrAny<std::is_integral>());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
159
tests/lib/meta/tuple-idx-adaptor-test.cpp
Normal file
159
tests/lib/meta/tuple-idx-adaptor-test.cpp
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
TupleIdxAdaptor(Test) - demonstrate building a static dispatcher table
|
||||
|
||||
Copyright (C)
|
||||
2026, 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 fun-trampoline-test.cpp
|
||||
** unit test \ref TupleIdxAdaptor_test
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#include "test/run.hpp"
|
||||
#include "test/test-helper.hpp"
|
||||
#include "lib/format-string.hpp"
|
||||
#include "lib/format-util.hpp"
|
||||
|
||||
#include "lib/meta/tuple-idx-adaptor.hpp"
|
||||
|
||||
|
||||
using test::showType;
|
||||
using std::string;
|
||||
using std::tuple;
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta {
|
||||
namespace test {
|
||||
|
||||
|
||||
|
||||
/*************************************************************//**
|
||||
* @test show run-time index access and iteration over tuples,
|
||||
* relying on a static function dispatcher table.
|
||||
* @see TupleIdxAdaptor_test
|
||||
*/
|
||||
class TupleIdxAdaptor_test : public Test
|
||||
{
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
demonstrate_simpleAccess();
|
||||
demonstrate_referenceAccess();
|
||||
demonstrate_TupleIteration();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** @test build adaptor to get run-time index access to a tuple
|
||||
*/
|
||||
void
|
||||
demonstrate_simpleAccess()
|
||||
{
|
||||
auto tup42 = std::make_tuple (short(4), long(2));
|
||||
CHECK (tup42 == "«tuple<short, long>»──(4,2)"_expect );
|
||||
|
||||
TupleIndexAdaptor idxAdapt{tup42};
|
||||
CHECK (idxAdapt.size() == 2);
|
||||
CHECK (idxAdapt[0] == 4);
|
||||
CHECK (idxAdapt[1] == 2);
|
||||
|
||||
using ResType = decltype(idxAdapt[0]);
|
||||
CHECK (showType<ResType>() == "long"_expect );
|
||||
} // got access by a common value-type
|
||||
|
||||
|
||||
|
||||
/** @test full access by reference to the data in the tuple is possible,
|
||||
* if the data types can be reconciled to a common reference type.
|
||||
*/
|
||||
void
|
||||
demonstrate_referenceAccess()
|
||||
{
|
||||
using Tup = std::tuple<int,int&,int>;
|
||||
|
||||
using TupApt = TupleIndexAdaptor<Tup>;
|
||||
// indeed provides access by LValue reference
|
||||
CHECK (TupApt::isRefAccess);
|
||||
CHECK (showType<TupApt::ResType>() == "int&"_expect );
|
||||
|
||||
int ii{23};
|
||||
TupApt itup{-1,ii,55};
|
||||
CHECK (itup == "iTup(-1,23,55)"_expect );
|
||||
|
||||
// can access members by subscript...
|
||||
CHECK (itup[0] == -1);
|
||||
CHECK (itup[1] == 23);
|
||||
CHECK (itup[2] == 55);
|
||||
|
||||
// yet can also manipulate through this accessor
|
||||
CHECK (++itup[0] == 0);
|
||||
CHECK ( itup[1]++ == 23);
|
||||
CHECK (++++itup[2] == 57);
|
||||
|
||||
// data in the tuple fields was indeed manipulated
|
||||
CHECK (itup == "iTup(0,24,57)"_expect );
|
||||
Tup copy{itup};
|
||||
CHECK (getElm<0>(copy) == 0);
|
||||
CHECK (getElm<1>(copy) == 24);
|
||||
CHECK (getElm<2>(copy) == 57);
|
||||
|
||||
// and since the second field in the tuple is actually a reference,
|
||||
// the manipulation did even affect the referred external variable...
|
||||
CHECK (ii == "24"_expect );
|
||||
++ii;
|
||||
CHECK (25 == itup[1]);
|
||||
CHECK (25 == getElm<1>(copy));
|
||||
}
|
||||
|
||||
|
||||
/** @test Since the TupleIndexAdaptor is subscriptable,
|
||||
* it can be used to build a run-time iterator over the tuple.
|
||||
* @remark this demo example is somewhat weird, since the iterator is built
|
||||
* based on the IdxStoreCore, which directly embeds the _source container_.
|
||||
* Thus, when the iterator is copied, the source is copied as well —
|
||||
* which violates several common assumptions regarding iterators.
|
||||
* However, when working carefully around this irregularity,
|
||||
* iteration and manipulation work as expected...
|
||||
*/
|
||||
void
|
||||
demonstrate_TupleIteration()
|
||||
{
|
||||
using Tup = tuple<char,char,char>;
|
||||
using TupApt = TupleIndexAdaptor<Tup>;
|
||||
using TupIter = IterableDecorator<IdxStoreCore<TupApt>>;
|
||||
|
||||
// this iterator actually embeds the source tuple
|
||||
TupIter tupIt{TupApt{'i','b','m'}};
|
||||
|
||||
// dereferencing this iterator provides access by reference
|
||||
CHECK (showType<decltype(*tupIt)>() == "char&"_expect );
|
||||
|
||||
// can iterate as expected
|
||||
CHECK (bool(tupIt));
|
||||
CHECK (*tupIt == "i"_expect );
|
||||
CHECK (util::join (tupIt, "·") == "i·b·m"_expect );
|
||||
|
||||
do
|
||||
--*tupIt; // manipulate through iterator
|
||||
while (++tupIt);
|
||||
|
||||
CHECK (not tupIt);
|
||||
// yet the manipulated data tuple is still sitting
|
||||
// inside of the (now invalidated) iterator...
|
||||
CHECK (tupIt == "iTup(h,a,l)"_expect );
|
||||
}
|
||||
};
|
||||
|
||||
LAUNCHER (TupleIdxAdaptor_test, "unit common");
|
||||
|
||||
|
||||
}}} // namespace lib::meta::test
|
||||
|
|
@ -85,7 +85,7 @@ namespace test {
|
|||
CHECK (showType< S1AA >() == "__and_<is_pointer<int>, is_pointer<double> >"_expect);
|
||||
CHECK (false == S1AA::value);
|
||||
|
||||
using S1OA = S1::OrAll<std::is_pointer>;
|
||||
using S1OA = S1::OrAny<std::is_pointer>;
|
||||
CHECK (showType< S1OA >() == "__or_<is_pointer<int>, is_pointer<double> >"_expect);
|
||||
CHECK (false == S1OA::value);
|
||||
|
||||
|
|
@ -108,7 +108,7 @@ namespace test {
|
|||
CHECK (showType< S0AA >() == "__and_<is_pointer<int*> >"_expect);
|
||||
CHECK (true == S0AA::value);
|
||||
|
||||
using S0OA = S0::OrAll<std::is_pointer>;
|
||||
using S0OA = S0::OrAny<std::is_pointer>;
|
||||
CHECK (showType< S0OA >() == "__or_<is_pointer<int*> >"_expect);
|
||||
CHECK (true == S0OA::value);
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ namespace test {
|
|||
CHECK (showType< S2AA >() == "__and_<is_pointer<int*>, is_pointer<int*>, is_pointer<int*> >"_expect);
|
||||
CHECK (true == S2AA::value);
|
||||
|
||||
using S2OA = S2::OrAll<std::is_pointer>;
|
||||
using S2OA = S2::OrAny<std::is_pointer>;
|
||||
CHECK (showType< S2OA >() == "__or_<is_pointer<int*>, is_pointer<int*>, is_pointer<int*> >"_expect);
|
||||
CHECK (true == S2OA::value);
|
||||
|
||||
|
|
@ -153,7 +153,7 @@ namespace test {
|
|||
CHECK (showType< S3AA >() == "__and_<is_pointer<int*>, is_pointer<long>, is_pointer<double*> >"_expect);
|
||||
CHECK (false == S3AA::value);
|
||||
|
||||
using S3OA = S3::OrAll<std::is_pointer>;
|
||||
using S3OA = S3::OrAny<std::is_pointer>;
|
||||
CHECK (showType< S3OA >() == "__or_<is_pointer<int*>, is_pointer<long>, is_pointer<double*> >"_expect);
|
||||
CHECK (true == S3OA::value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ namespace test {
|
|||
|
||||
using ::test::showType;
|
||||
using lib::meta::typeSymbol;
|
||||
using lib::meta::is_Tuple;
|
||||
using lib::meta::is_Tuple_v;
|
||||
using std::decay_t;
|
||||
using std::vector;
|
||||
using std::get;
|
||||
|
|
@ -183,7 +183,7 @@ namespace test {
|
|||
CHECK ( e2.result);
|
||||
|
||||
using SeqRes = decltype(e2)::Result; // Note: the result type depends on the actual syntax construction
|
||||
CHECK (is_Tuple<SeqRes>()); // Result model from sequence is the tuple of terminal results
|
||||
CHECK (is_Tuple_v<SeqRes>); // Result model from sequence is the tuple of terminal results
|
||||
auto& [r1,r2] = *e2.result;
|
||||
CHECK (r1.str() == "hello"_expect);
|
||||
CHECK (r2.str() == "world"_expect);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
#include "lib/format-string.hpp"
|
||||
#include "lib/format-cout.hpp"
|
||||
#include "lib/format-util.hpp"
|
||||
#include "lib/meta/tuple-accessor.hpp"
|
||||
#include "lib/meta/tuple-helper.hpp"
|
||||
#include "lib/iter-explorer.hpp"
|
||||
|
||||
#include <string>
|
||||
|
|
@ -108,7 +108,7 @@ namespace test{
|
|||
report (Literal func, ARGS&&...args)
|
||||
{
|
||||
return string(func)
|
||||
+ meta::dump (make_tuple (forward<ARGS>(args)...));
|
||||
+ meta::joinTupleParen (make_tuple (forward<ARGS>(args)...));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue