LUMIERA.clone/tests/library/meta/tuple-helper-test.cpp
Ichthyostega 170b68ac5c Upgrade: further extend usage of the tuple_like concept + generic apply
This changeset removes various heuristics and marker-traits
by a constraint to tuple_like types. Furthermore, several usages
of `apply` can thereby be generalised to work on any tuple_like.

This generalisation is essential for the passing generic data blocks
via `FeedManifold` into the node invocation
2025-07-02 01:16:08 +02:00

372 lines
15 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
TupleHelper(Test) - verify helpers for working with tuples and type sequences
Copyright (C)
2009, 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-helper-test.cpp
** Interplay of typelists, type tuples and std::tuple.
**
** @see tuple-helper.hpp
** @see function-closure.hpp
** @see control::CmdClosure real world usage example
**
*/
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/meta/tuple-helper.hpp"
#include "meta/typelist-diagnostics.hpp"
#include "meta/tuple-diagnostics.hpp"
#include "lib/format-string.hpp"
#include "lib/format-cout.hpp"
#include "lib/hetero-data.hpp"
#include "lib/test/diagnostic-output.hpp"////////////////TODO
#include <string>
using lib::test::showSizeof;
using util::toString;
using util::_Fmt;
using std::is_same_v;
using std::make_tuple;
using std::get;
namespace lib {
namespace meta {
namespace test {
namespace { // test data
typedef Types< Num<1>
, Num<3>
, Num<5>
> Types1;
typedef Types< Num<2>
, Num<4>
> Types2;
typedef Types< Num<7>> Types3;
} // (End) test data
/*********************************************************************//**
* @test Cover various aspects of the integration of our type sequences
* with the tuple type from the standard library
* - verify our generic tuple access decorator
* - verify generating tuple types from type list processing
* - TODO more helpers to come
*/
class TupleHelper_test : public Test
{
virtual void
run (Arg)
{
check_diagnostics();
check_tuple_from_Typelist();
demonstrate_generic_iteration();
verify_tuple_like_concept();
}
/** verify the test input data
* @see TypeListManip_test::check_diagnostics()
* for an explanation of the DISPLAY macro
*/
void
check_diagnostics ()
{
typedef Types1::List L1;
typedef Types2::List L2;
typedef Types3::List L3;
DISPLAY (L1);
DISPLAY (L2);
DISPLAY (L3);
typedef Tuple<Types1> Tup1;
Tup1 tup1x (Num<1>(11), Num<3>(), 55);
DISPLAY (Tup1); // prints the type
DUMPVAL (Tup1()); // prints the contents
DUMPVAL (tup1x);
EXPECT (Types1, "-<1>-<3>-<5>-");
EXPECT (Tup1, "TUPLE-<1>-<3>-<5>-");
CHECK (get<2>(tup1x) == Num<5>{55});
CHECK (toString(tup1x) == "«tuple<Num<1>, Num<3>, Num<5> >»──({11},(3),{55})"_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
* - our generic string conversion is extended to work with tuples
*/
void
check_tuple_from_Typelist()
{
using L1 = Types1::List; // ... start from existing Typelist...
using T_L1 = Tuple<L1>; // derive a tuple type from this typelist
using Seq1 = RebindTupleTypes<T_L1>::Seq;
// extract the underlying type sequence
EXPECT (T_L1, "TUPLE-<1>-<3>-<5>-");
EXPECT (Seq1, "-<1>-<3>-<5>-");
T_L1 tup1; // can be instantiated at runtime (and is just a std:tuple)
CHECK (toString(tup1) == "«tuple<Num<1>, Num<3>, Num<5> >»──((1),(3),(5))"_expect);
using Prepend = Tuple<Node<int, L1>>;
// another ListType based tuple created by prepending
EXPECT (Prepend, "TUPLE-<i>-<1>-<3>-<5>-");
Prepend prep (22, 11,33,Num<5>());
CHECK (toString(prep) == "«tuple<int, Num<1>, Num<3>, Num<5> >»──(22,{11},{33},(5))"_expect);
using NulT = Tuple<Types<> >; // plain-flat empty Tuple
using NulL = Tuple<Nil>; // list-style empty Tuple
NulT nulT; // and these, too, can be instantiated
NulL nulL;
CHECK (toString(nulT) == "«tuple<>»──()"_expect);
CHECK (toString(nulL) == "«tuple<>»──()"_expect);
CHECK ((is_same<NulT, NulL>()));
using S4 = struct{int a,b,c,d;}; // expect this to have the same memory layout
CHECK (sizeof(S4) == sizeof(prep));
CHECK (1 == sizeof(nulL)); // ...minimal storage, as expected
CHECK (is_Tuple<T_L1>());
CHECK (is_Tuple<Prepend>());
CHECK (is_Tuple<NulT>());
CHECK (not is_Tuple<Seq1>());
cout << tup1 <<endl // these automatically use our generic string conversion
<< prep <<endl
<< nulL <<endl;
cout << showSizeof(tup1) <<endl
<< showSizeof(prep) <<endl
<< showSizeof(nulT) <<endl
<< showSizeof(nulL) <<endl;
CHECK (sizeof(prep) == sizeof(int)+sizeof(Num<1>)+sizeof(Num<3>)+sizeof(Num<5>));
}
/** @test demonstrate generic tuple iteration mechanisms
* - apply a generic lambda to each element of a tuple
* - iterate over all index numbers of a tuple (or tuple-like)
* @remark the _iteration_ happens at compile-time, i.e. the given
* lambda-template is instantiated for each type of the tuple;
* however, at runtime, each of these instantiations is invoked
* in sequence, performing the body of the λ-function, which may
* access the actual data value for each of the tuple elements.
*/
void
demonstrate_generic_iteration()
{
auto tup = make_tuple (1, 2.3, '4');
using Tup = decltype(tup);
CHECK (toString(tup) == "«tuple<int, double, char>»──(1,2.3,4)"_expect);
string dump{};
CHECK (dump == "");
// apply λ-generic to each element...
forEach (tup, [&](auto elm){ dump += "|"+toString(elm); });
CHECK (dump == "|1|2.3|4"_expect);
// apply λ-generic to each element and build new tuple from results
auto tupStr = mapEach (tup, [](auto const& elm){ return toString(elm); });
CHECK (tupStr == "«tuple<string, string, string>»──(1,2.3,4)"_expect);
_Fmt showElm{"|%d|▷%s◁"};
dump = "";
// apply λ-generic with (constexpr) index
forEachIDX<Tup> ([&](auto idx)
{
using Idx = decltype(idx);
using Iii = std::integral_constant<size_t, idx.value>;
CHECK ((is_same_v<Idx, Iii>));
dump += showElm % uint(idx) % get<idx>(tup);
});
CHECK (dump == "|0|▷1◁|1|▷2.3◁|2|▷4◁"_expect);
// apply λ-generic and combine results
auto boolFun = [&](auto idx){ return bool(get<idx>(tup)); };
CHECK ( andAllIDX<Tup> (boolFun));
get<0>(tup) = 0; // ◁————————————————————— demonstrate that a run-time evaluation happens
CHECK (not andAllIDX<Tup> (boolFun));
CHECK ( orAnyIDX<Tup> (boolFun));
// can use this mechanism also for sole compile-time computation
auto integralCheckFun = [](auto idx){ return std::is_integral_v<std::tuple_element_t<idx,Tup>>;};
CHECK (not andAllIDX<Tup> (integralCheckFun));
CHECK ( orAnyIDX<Tup> (integralCheckFun));
// 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>());
}
template<typename X>
string
render()
{
return lib::test::showType<X>();
}
template<tuple_like X>
string
render()
{
string res{"Tup"};
res +="("+toString(std::tuple_size_v<X>)+") : "+ lib::test::showType<X>();
lib::meta::forEachIDX<X> ([&](auto i)
{
using Elm = std::tuple_element_t<i, X>;
res += ""+ toString(uint(i)) + ": " + lib::test::showType<Elm>();
});
return res;
}
/** @test verify construction of a concept to detect _tuple-like_ classes,
* which conform to the »tuple protocol« and can thus be used
* in structural bindings
* - the size of such an entity can be detected at compile time
* - essentially, these can be considered as _product types_ — which implies
* that there are N element types
* - access to the corresponding member data is possible either through a
* `get` member function or free function detected by ADL
*/
void
verify_tuple_like_concept()
{
using Tup = std::tuple<long,short>;
using Arr = std::array<int,3>;
using Het = lib::HeteroData<int,string>::Chain<short>::ChainExtent<bool,Nil>::ChainType;
CHECK ( tuple_sized<Tup> );
CHECK ( tuple_sized<Arr> );
CHECK ( tuple_sized<Het> );
CHECK (not tuple_sized<int> );
CHECK ( (tuple_element_accessible<Tup,0>));
// CHECK ( (tuple_element_accessible<Tup,2>));
CHECK ( (tuple_element_accessible<Het,0>));
CHECK ( tuple_accessible<Tup> );
CHECK ( tuple_accessible<Arr> );
CHECK ( tuple_accessible<Het> );
CHECK (not tuple_accessible<int> );
// verify the concept detects various tuple-like types
CHECK ( tuple_like<Tup> );
CHECK ( tuple_like<Arr> );
CHECK ( tuple_like<Het> );
CHECK (( tuple_like<Tuple<Types<int,float>::List>>));
CHECK (( tuple_like<std::tuple<int> >));
CHECK (( tuple_like<std::tuple<int,char,long> >));
CHECK (( tuple_like<std::tuple<> >));
CHECK (( tuple_like<std::pair<short,long> >));
CHECK (( tuple_like<std::array<short,5> >));
CHECK (( tuple_like<std::array<long,0> >));
CHECK (( tuple_like<HeteroData<size_t> >));
CHECK (( tuple_like<HeteroData<int,char> >));
CHECK (( tuple_like<HeteroData<> >));
// verify arbitrary non-structured types
CHECK (not tuple_like<int >);
CHECK (not tuple_like<void >);
CHECK (not tuple_like<void* >);
CHECK (not tuple_like<const void* >);
CHECK (not tuple_like<const int >);
CHECK (not tuple_like<int >);
CHECK (not tuple_like<int & >);
CHECK (not tuple_like<int const & >);
CHECK (not tuple_like<int const * >);
CHECK (not tuple_like<int * >);
CHECK (not tuple_like<int * const >);
CHECK (not tuple_like<int * const & >);
CHECK (not tuple_like<int * & >);
CHECK (not tuple_like<int * && >);
CHECK (not tuple_like<int && >);
CHECK (not tuple_like<int const && >);
CHECK (not tuple_like<double >);
CHECK (not tuple_like<string >);
CHECK((not tuple_like<Nil >));
CHECK((not tuple_like<Node<short,Nil>>));
// the tuple, the array and the HeteroData are tuple-like,
// and will be handled by a special overload, exploiting the additional features
CHECK (render<Tup>() == "Tup(2) : tuple<long, short> ▷0: long ▷1: short"_expect);
CHECK (render<Arr>() == "Tup(3) : array<int, 3ul> ▷0: int ▷1: int ▷2: int"_expect);
CHECK (render<Het>() == "Tup(5) : HeteroData<Node<StorageFrame<0ul, int, string>, "
"Node<StorageFrame<1ul, short>, "
"Node<StorageFrame<2ul, bool, Nil>, Nil> > > > "
"▷0: int ▷1: string ▷2: short ▷3: bool ▷4: Nil"_expect);
// the plain type `int` is not tuple-like and thus the fallback case is picked
CHECK (render<int>() == "int"_expect);
CHECK (std::tuple_size_v<const Tup> == 2 );
using Elm1 = std::tuple_element_t<1, const Tup>;
CHECK (lib::test::showType<Elm1>() == "const short"_expect);
// note: a const tuple will add const qualification to each element type
using TupConstSeq = lib::meta::ElmTypes<const Tup>::Seq;
CHECK (lib::test::showType<TupConstSeq>() == "Types<long const, short const>"_expect);
// a unified access function `getElm`
// which works both with access by member or free function
using T1 = decltype(lib::meta::getElm<0> (std::declval<Tup>()));
CHECK (lib::test::showType<T1>() == "long &&"_expect);
using T2 = decltype(lib::meta::getElm<0> (std::declval<Tup&>()));
CHECK (lib::test::showType<T2>() == "long&"_expect);
using T3 = decltype(lib::meta::getElm<0> (std::declval<Tup const&>()));
CHECK (lib::test::showType<T3>() == "long const&"_expect);
using H1 = decltype(lib::meta::getElm<4> (std::declval<Het>()));
CHECK (lib::test::showType<H1>() == "Nil"_expect);
using H2 = decltype(lib::meta::getElm<4> (std::declval<Het&>()));
CHECK (lib::test::showType<H2>() == "Nil&"_expect);
using H3 = decltype(lib::meta::getElm<4> (std::declval<Het const&>()));
CHECK (lib::test::showType<H3>() == "Nil const&"_expect);
}
};
/** Register this test class... */
LAUNCHER (TupleHelper_test, "unit meta");
}}} // namespace lib::meta::test