LUMIERA.clone/tests/lib/meta/tuple-idx-adaptor-test.cpp
Ichthyostega 0760525ea5 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
2026-04-07 06:32:23 +02:00

159 lines
5.1 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.

/*
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