LUMIERA.clone/tests/lib/meta/common-result-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

224 lines
9.7 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.

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