Library: investigate design for an opaque inline functor
This is a design study to find out what would be required to build an opaque function wrapper, similar to `std::function`, yet without using any heap memory ever. Such a building block would allow for some interesting options for a well structured design of elaborate data structures. This changeset provides an implementation draft. __Two important findings to note__: * it is impossible to get away with just a single function pointer * because at the very least we need to invoke the destructor of the embedded delegate. Thus something akin to a "manager" is required. * this implies that the component would not be so efficient as I hoped, since it requires two pointers and requires to emit code for each type of delegate. * by making the component **NonCopyable**, I avoided the complexities of virtual copy support. In case the latter is desired, it would be much simpler to switch to a classical OO approach with a VTable and use our `PolymorphicValue` wrapper to handle copy and cloning.
This commit is contained in:
parent
0760525ea5
commit
a0d83b4fe8
15 changed files with 995 additions and 244 deletions
|
|
@ -235,4 +235,14 @@ unpack iterator into tuple::
|
|||
- see tuple-record-init-test.cpp
|
||||
- used in 'command-simple-closure.hpp' to receive parameter values sent via UI-Bus and package them
|
||||
into a tuple for invocation of a Steam-Layer command.
|
||||
+
|
||||
index and iterate over tuple elements::
|
||||
This is another example of bridging the compile-time / runtime barrier, using a trampoline.
|
||||
Obviously such a solution requires that it is somehow possible to define a generic functor / λ that
|
||||
can accept any of the tuple elements. And, in order to build a runtime index access, we additionally
|
||||
need to convert each of the tuple elements into a _common reconciled type.
|
||||
+
|
||||
- If such a type exists, can be figured out with the help of the `meta::CommonReturn` _metafunction_
|
||||
- `lib::meta::TupleIdxAdaptor` provides the necessary machinery to define a subscript `operator[]`
|
||||
- see `TupleIdxAdaptor_test` for some usage examples, including a runtime iteration...
|
||||
|
||||
|
|
|
|||
237
src/lib/meta/fun-inline.hpp
Normal file
237
src/lib/meta/fun-inline.hpp
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
FUN-INLINE.hpp - abstract functor with local delegate
|
||||
|
||||
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-inline.hpp
|
||||
** A generic abstracted functor with the implementation stored locally.
|
||||
** This template is an implementation building block and allows to create
|
||||
** an abstraction barrier, similar to what std::function does. In contrast
|
||||
** to the latter however, FunInline places the actual implementation delegate
|
||||
** into an embedded opaque buffer, never using any heap memory.
|
||||
**
|
||||
** @todo WIP-WIP 4/2026 this is more like a conceptual draft,
|
||||
** not sure where this design exercise will lead to.
|
||||
** @warning after investigating the implementation technique to some degree,
|
||||
** it became clear that **copy support** is the greatest challenge.
|
||||
** All »the rest« might be surprisingly simple to implement, yet
|
||||
** the demand for a movable / copyable value-object effectively
|
||||
** translates into something like a VTable — and the result
|
||||
** would be similar to lib::PolymorphicValue — just with
|
||||
** some special binding to the function operator.
|
||||
** For this reason, FunInline is now declared to be util::NonCopyable
|
||||
** @todo 4/2026 I've finally got this draft hammered out.
|
||||
** It is far from what I wanted to achieve...
|
||||
** Notably a second function pointer is used
|
||||
** That is ugly, but it does the job.
|
||||
**
|
||||
** @see FunInline_test
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LIB_META_FUN_INLINE_H_
|
||||
#define LIB_META_FUN_INLINE_H_
|
||||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
#include "lib/meta/function.hpp"
|
||||
#include "lib/meta/typeseq-util.hpp"
|
||||
#include "lib/meta/variadic-helper.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta{
|
||||
namespace err = lumiera::error;
|
||||
|
||||
using std::forward;
|
||||
|
||||
namespace test { class FunInline_test; }
|
||||
|
||||
/**
|
||||
* An opaque front-end for a functor stored inline.
|
||||
* The type of the implementation delegate is erased
|
||||
* from the type, retaining only the generic signature.
|
||||
* @note this wrapper object is defined as NonCopyable,
|
||||
* because anything else would require a VTable,
|
||||
* and raise serious concerns regarding identity.
|
||||
* @warning deliberately **not threadsafe**
|
||||
* @see FunInline_test
|
||||
*/
|
||||
template<class SIG
|
||||
,size_t siz
|
||||
,typename ALI = int64_t
|
||||
>
|
||||
class FunInline
|
||||
{
|
||||
static_assert (!sizeof(SIG), "function signature required");
|
||||
};
|
||||
|
||||
|
||||
template<typename RET, typename...ARGS
|
||||
,size_t siz
|
||||
,typename ALI
|
||||
>
|
||||
class FunInline<RET(ARGS...), siz, ALI>
|
||||
: util::NonCopyable
|
||||
{
|
||||
using _F = _Fun<RET(ARGS...)>;
|
||||
static_assert (_F{}, "function signature required");
|
||||
|
||||
using Sig = _F::Sig;
|
||||
using Ret = _F::Ret;
|
||||
using Args = _F::Args;
|
||||
using RArgs = ElmTypes<Args>::template Apply<std::add_rvalue_reference_t>;
|
||||
using IArgs = Prepend<void*, RArgs>::Seq;
|
||||
using ISig = BuildFunType<Ret, IArgs>::Sig;
|
||||
using Dtor = void(void*);
|
||||
|
||||
/** virtualised delegation through function pointer(s) */
|
||||
ISig* invoker_{nullptr};
|
||||
Dtor* destroy_{nullptr};
|
||||
|
||||
/** opaque storage buffer to emplace the functor delegate */
|
||||
alignas(ALI) mutable
|
||||
std::byte buf_[siz];
|
||||
|
||||
|
||||
/**
|
||||
* The Invoker template is instantiated with the type of the concrete
|
||||
* delegate functor (which resides in the opaque storage buffer).
|
||||
* The purpose of this template is to cause generation of a static
|
||||
* delegation function that can be invoked through the #invoker_ pointer.
|
||||
* In the typical usage situation, the delegate type \a FUNC is implemented
|
||||
* mostly with inline functions, and thus the bulk of the actual implementation
|
||||
* code will be emitted into this static delegation function. The template
|
||||
* \ref InvokerBuilder provides the blueprint for this code generation.
|
||||
*/
|
||||
template<class FUN>
|
||||
struct Invoker
|
||||
{
|
||||
static Ret
|
||||
dispatch (void* instance, ARGS&&...args)
|
||||
{
|
||||
return functor(instance) (forward<ARGS> (args)...);
|
||||
}
|
||||
|
||||
static void
|
||||
destroy (void* instance)
|
||||
{
|
||||
if (instance)
|
||||
functor(instance).~FUN();
|
||||
}
|
||||
|
||||
static FUN&
|
||||
functor (void* instance)
|
||||
{
|
||||
REQUIRE (instance);
|
||||
return * static_cast<FUN*> (instance);
|
||||
}
|
||||
|
||||
using Sig = _F::Sig;
|
||||
using ISig = decltype(dispatch);
|
||||
};
|
||||
|
||||
/** allow the Unit test to poke into internals */
|
||||
friend class FunInline_test;
|
||||
|
||||
|
||||
|
||||
public:
|
||||
~FunInline(){ destroy(); }
|
||||
|
||||
FunInline() = default;
|
||||
|
||||
template<class FUNC>
|
||||
FunInline (FUNC&& functor)
|
||||
{
|
||||
using Functor = std::decay_t<FUNC>;
|
||||
emplace<Functor> (forward<FUNC> (functor));
|
||||
activate<Functor>();
|
||||
}
|
||||
|
||||
// NonCopyable
|
||||
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return bool(destroy_) and bool(invoker_);
|
||||
}
|
||||
|
||||
|
||||
/* == Functor interface == */
|
||||
|
||||
Ret
|
||||
operator() (ARGS&&... args)
|
||||
{
|
||||
if (not invoker_)
|
||||
throw err::State{"Attempt to invoke inactive function."};
|
||||
|
||||
return (*invoker_) (&buf_, forward<ARGS> (args)...);
|
||||
}
|
||||
|
||||
|
||||
private: /* == Instance management == */
|
||||
|
||||
void
|
||||
destroy()
|
||||
{
|
||||
if (destroy_)
|
||||
(*destroy_) (&buf_);
|
||||
destroy_ = nullptr;
|
||||
invoker_ = nullptr;
|
||||
}
|
||||
|
||||
template<class FUNC>
|
||||
void
|
||||
activate()
|
||||
{
|
||||
ASSERT_VALID_SIGNATURE (FUNC, Sig);
|
||||
REQUIRE (!destroy_ and !invoker_, "Lifecycle logic broken");
|
||||
|
||||
destroy_ = Invoker<FUNC>::destroy;
|
||||
invoker_ = Invoker<FUNC>::dispatch;
|
||||
}
|
||||
|
||||
/** Abbreviation for placement new */
|
||||
template<class TY, typename...INIT>
|
||||
TY&
|
||||
emplace (INIT&& ...args)
|
||||
{
|
||||
static_assert (siz >= sizeof(TY), "Embedded storage buffer too small");
|
||||
static_assert (alignof(ALI) >= alignof(TY), "Alignment requirements not fulfilled");
|
||||
|
||||
destroy();
|
||||
try {
|
||||
return *new(&buf_) TY {std::forward<INIT> (args)...};
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ENSURE (not destroy_);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// @todo unused ... not required?
|
||||
template<typename XXX>
|
||||
XXX&
|
||||
accessAs() const
|
||||
{
|
||||
return * std::launder (reinterpret_cast<XXX*> (&buf_));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}}// namespace lib::meta
|
||||
#endif /*LIB_META_FUN_INLINE_H_*/
|
||||
|
|
@ -49,12 +49,12 @@ namespace test{
|
|||
* Macro to print types and expressions to STDOUT,
|
||||
* using Lumiera's string conversion framework
|
||||
*/
|
||||
#define SHOW_TYPE(_TY_) \
|
||||
cout << "typeof( " << STRINGIFY(_TY_) << " )= " << ::test::showType<_TY_>() <<endl;
|
||||
#define SHOW_EXPR(_XX_) \
|
||||
cout << "#--◆--# " << STRINGIFY(_XX_) << " ? = " << util::toString(_XX_) <<endl;
|
||||
#define SHOW_EXPL(_XX_) \
|
||||
cout << "#--◆--# " << STRINGIFY(_XX_) << " ? = " << boost::lexical_cast<string>(_XX_) <<endl;
|
||||
#define SHOW_TYPE(_TY_) \
|
||||
cout << "#--◆--# " << "showType<"<< STRINGIFY(_TY_) <<">()" << " ? = " << ::test::showType<_TY_>() <<endl;
|
||||
|
||||
|
||||
#endif /*TEST_DIAGNOSTIC_OUTPUT_H*/
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ namespace test {
|
|||
class Dummy
|
||||
: util::MoveAssign
|
||||
{
|
||||
int val_;
|
||||
int id_;
|
||||
|
||||
/** to verify ctor/dtor calls */
|
||||
static long _local_checksum;
|
||||
|
|
@ -54,9 +54,9 @@ namespace test {
|
|||
void
|
||||
init()
|
||||
{
|
||||
checksum() += val_;
|
||||
checksum() += id_;
|
||||
if (_throw_in_ctor)
|
||||
throw val_;
|
||||
throw id_;
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
@ -65,34 +65,34 @@ namespace test {
|
|||
|
||||
virtual ~Dummy() ///< can act as interface
|
||||
{
|
||||
if (val_ != DEFUNCT)
|
||||
checksum() -= val_;
|
||||
val_ = DEAD;
|
||||
if (id_ != DEFUNCT)
|
||||
checksum() -= id_;
|
||||
id_ = DEAD;
|
||||
}
|
||||
|
||||
Dummy ()
|
||||
: val_{1 + rani (100'000'000)}
|
||||
: id_{1 + rani (100'000'000)}
|
||||
{ init(); }
|
||||
|
||||
Dummy (int v)
|
||||
: val_(v)
|
||||
: id_(v)
|
||||
{ init(); }
|
||||
|
||||
friend void
|
||||
swap (Dummy& dum1, Dummy& dum2) ///< checksum neutral
|
||||
{
|
||||
std::swap (dum1.val_, dum2.val_);
|
||||
std::swap (dum1.id_, dum2.id_);
|
||||
}
|
||||
|
||||
Dummy (Dummy const& o)
|
||||
: Dummy{o.val_}
|
||||
: Dummy{o.id_}
|
||||
{ }
|
||||
|
||||
Dummy (Dummy && oDummy) noexcept
|
||||
: Dummy(0)
|
||||
{
|
||||
swap (*this, oDummy);
|
||||
oDummy.val_ = DEFUNCT;
|
||||
oDummy.id_ = DEFUNCT;
|
||||
}
|
||||
|
||||
Dummy&
|
||||
|
|
@ -107,20 +107,20 @@ namespace test {
|
|||
virtual long
|
||||
calc (int i)
|
||||
{
|
||||
return val_+i;
|
||||
return id_+i;
|
||||
}
|
||||
|
||||
int
|
||||
getVal() const
|
||||
getID() const
|
||||
{
|
||||
return val_;
|
||||
return id_;
|
||||
}
|
||||
|
||||
void
|
||||
setVal (int newVal)
|
||||
setID (int newVal)
|
||||
{
|
||||
checksum() += newVal - val_;
|
||||
val_ = newVal;
|
||||
checksum() += newVal - id_;
|
||||
id_ = newVal;
|
||||
}
|
||||
|
||||
static long&
|
||||
|
|
|
|||
|
|
@ -138,6 +138,11 @@ return: 0
|
|||
END
|
||||
|
||||
|
||||
TEST "opaque functor value object" FunInline_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
TEST "generate functor trampoline" FunTrampoline_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
|
|
|||
|
|
@ -147,13 +147,13 @@ namespace test{
|
|||
Dummy::checksum() = 0;
|
||||
{ // track instances by checksum...
|
||||
Dummy dummy;
|
||||
auto rr = dummy.getVal();
|
||||
auto rr = dummy.getID();
|
||||
CHECK (rr == Dummy::checksum());
|
||||
CHECK (rr > 0);
|
||||
|
||||
using BB = BranchCase<string,Dummy>;
|
||||
BB bb1{1, dummy};
|
||||
CHECK (bb1.get<1>().getVal() == rr);
|
||||
CHECK (bb1.get<1>().getID() == rr);
|
||||
CHECK (2*rr == Dummy::checksum()); // got two instances due to copy-init
|
||||
|
||||
BB bb2{0, "dummy"};
|
||||
|
|
@ -161,22 +161,22 @@ namespace test{
|
|||
|
||||
swap (bb1,bb2);
|
||||
CHECK (bb1.get<0>() == "dummy");
|
||||
CHECK (bb2.get<1>().getVal() == rr);
|
||||
CHECK (bb2.get<1>().getID() == rr);
|
||||
CHECK (2*rr == Dummy::checksum());
|
||||
|
||||
bb1 = bb2;
|
||||
CHECK (bb1.get<1>().getVal() == rr);
|
||||
CHECK (bb1.get<1>().getID() == rr);
|
||||
CHECK (3*rr == Dummy::checksum()); // assignment by copy
|
||||
|
||||
bb2 = move(bb1); // move-assignment
|
||||
CHECK (2*rr == Dummy::checksum()); // existing instance destroyed properly
|
||||
CHECK (bb2.get<1>().getVal() == rr);
|
||||
CHECK (bb1.get<1>().getVal() == Dummy::DEFUNCT);
|
||||
CHECK (bb2.get<1>().getID() == rr);
|
||||
CHECK (bb1.get<1>().getID() == Dummy::DEFUNCT);
|
||||
|
||||
bb2 = BB{1,Dummy()}; // wipes out the other copy
|
||||
auto rr2 = bb2.get<1>().getVal(); // but implants a different one
|
||||
auto rr2 = bb2.get<1>().getID(); // but implants a different one
|
||||
CHECK (rr+rr2 == Dummy::checksum());
|
||||
CHECK (rr == dummy.getVal());
|
||||
CHECK (rr == dummy.getID());
|
||||
}// leave scope: invoke dtors here
|
||||
|
||||
CHECK (0 == Dummy::checksum());
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ namespace test{
|
|||
virtual void
|
||||
nextResult(Pos& num)
|
||||
{
|
||||
uint current = num->getVal();
|
||||
uint current = num->getID();
|
||||
if (maxNum_ <= current)
|
||||
num = 0;
|
||||
else
|
||||
|
|
@ -203,11 +203,11 @@ namespace test{
|
|||
CHECK (5 == elements.size());
|
||||
CHECK (0 != Dummy::checksum());
|
||||
|
||||
CHECK (Dummy::checksum() == elements[0].getVal()
|
||||
+ elements[1].getVal()
|
||||
+ elements[2].getVal()
|
||||
+ elements[3].getVal()
|
||||
+ elements[4].getVal());
|
||||
CHECK (Dummy::checksum() == elements[0].getID()
|
||||
+ elements[1].getID()
|
||||
+ elements[2].getID()
|
||||
+ elements[3].getID()
|
||||
+ elements[4].getID());
|
||||
|
||||
elements.clear();
|
||||
CHECK (isnil (elements));
|
||||
|
|
@ -240,7 +240,7 @@ namespace test{
|
|||
List::iterator ii = elements.begin();
|
||||
while (ii)
|
||||
{
|
||||
CHECK (check == ii->getVal());
|
||||
CHECK (check == ii->getID());
|
||||
CHECK (check == ii->calc(+5) - 5);
|
||||
--check;
|
||||
++ii;
|
||||
|
|
@ -254,7 +254,7 @@ namespace test{
|
|||
List::const_iterator cii = const_elm.begin();
|
||||
while (cii)
|
||||
{
|
||||
CHECK (check == cii->getVal());
|
||||
CHECK (check == cii->getID());
|
||||
--check;
|
||||
++cii;
|
||||
}
|
||||
|
|
@ -293,49 +293,49 @@ namespace test{
|
|||
|
||||
list.emplace<Nummy>(1);
|
||||
CHECK (not isnil (list));
|
||||
CHECK (1 == list[0].getVal());
|
||||
CHECK (1 == list[0].getID());
|
||||
CHECK (1 == Dummy::checksum());
|
||||
list.reverse();
|
||||
CHECK (1 == Dummy::checksum());
|
||||
CHECK (1 == list[0].getVal());
|
||||
CHECK (1 == list[0].getID());
|
||||
CHECK (not isnil (list));
|
||||
|
||||
list.emplace<Nummy>(2);
|
||||
CHECK (not isnil (list));
|
||||
CHECK (2 == list.size());
|
||||
CHECK (2 == list[0].getVal());
|
||||
CHECK (2 == list[0].getID());
|
||||
CHECK (2+1 == Dummy::checksum());
|
||||
list.reverse();
|
||||
CHECK (1+2 == Dummy::checksum());
|
||||
CHECK (1 == list[0].getVal());
|
||||
CHECK (1 == list[0].getID());
|
||||
CHECK (2 == list.size());
|
||||
|
||||
list.emplace<Nummy>(3);
|
||||
CHECK (3 == list.size());
|
||||
CHECK (3 == list.top().getVal());
|
||||
CHECK (3 == list.top().getID());
|
||||
CHECK (3+1+2 == Dummy::checksum());
|
||||
list.reverse();
|
||||
CHECK (2 == list[0].getVal());
|
||||
CHECK (1 == list[1].getVal());
|
||||
CHECK (3 == list[2].getVal());
|
||||
CHECK (2 == list[0].getID());
|
||||
CHECK (1 == list[1].getID());
|
||||
CHECK (3 == list[2].getID());
|
||||
List::iterator ii = list.begin();
|
||||
CHECK (2 == ii->getVal());
|
||||
CHECK (2 == ii->getID());
|
||||
++ii;
|
||||
CHECK (1 == ii->getVal());
|
||||
CHECK (1 == ii->getID());
|
||||
++ii;
|
||||
CHECK (3 == ii->getVal());
|
||||
CHECK (3 == ii->getID());
|
||||
++ii;
|
||||
CHECK (isnil (ii));
|
||||
CHECK (2+1+3 == Dummy::checksum());
|
||||
|
||||
list.emplace<Nummy>(4);
|
||||
CHECK (4 == list.top().getVal());
|
||||
CHECK (3 == list[3].getVal());
|
||||
CHECK (4 == list.top().getID());
|
||||
CHECK (3 == list[3].getID());
|
||||
list.reverse();
|
||||
CHECK (3 == list[0].getVal());
|
||||
CHECK (1 == list[1].getVal());
|
||||
CHECK (2 == list[2].getVal());
|
||||
CHECK (4 == list[3].getVal());
|
||||
CHECK (3 == list[0].getID());
|
||||
CHECK (1 == list[1].getID());
|
||||
CHECK (2 == list[2].getID());
|
||||
CHECK (4 == list[3].getID());
|
||||
CHECK (3+1+2+4 == Dummy::checksum());
|
||||
}
|
||||
CHECK (0 == Dummy::checksum());
|
||||
|
|
@ -370,9 +370,9 @@ namespace test{
|
|||
CHECK (3 == elements.size());
|
||||
CHECK (22+44+66 == Dummy::checksum()); // not altered: we're referring the originals
|
||||
|
||||
CHECK (66 == elements[0].getVal());
|
||||
CHECK (44 == elements[1].getVal());
|
||||
CHECK (22 == elements[2].getVal());
|
||||
CHECK (66 == elements[0].getID());
|
||||
CHECK (44 == elements[1].getID());
|
||||
CHECK (22 == elements[2].getID());
|
||||
CHECK (isSameObject(n2, elements[2]));
|
||||
CHECK (isSameObject(n4, elements[1]));
|
||||
CHECK (isSameObject(n6, elements[0]));
|
||||
|
|
@ -403,8 +403,8 @@ namespace test{
|
|||
CHECK (1+2 == Dummy::checksum());
|
||||
CHECK (2 == elements.size());
|
||||
|
||||
CHECK (2 == elements[0].getVal());
|
||||
CHECK (1 == elements[1].getVal());
|
||||
CHECK (2 == elements[0].getID());
|
||||
CHECK (1 == elements[1].getID());
|
||||
|
||||
elements.clear();
|
||||
CHECK (0 == Dummy::checksum());
|
||||
|
|
@ -430,7 +430,7 @@ namespace test{
|
|||
List::iterator ii = elements.begin();
|
||||
while (ii)
|
||||
{
|
||||
CHECK (check == ii->getVal());
|
||||
CHECK (check == ii->getID());
|
||||
--check;
|
||||
++ii;
|
||||
}
|
||||
|
|
@ -513,9 +513,9 @@ namespace test{
|
|||
CHECK (sum(9) == Dummy::checksum());
|
||||
|
||||
CHECK (3 == elements.size());
|
||||
CHECK (1+2 == elements[2].getVal());
|
||||
CHECK (3+4+5 == elements[1].getVal());
|
||||
CHECK (6+7+8+9 == elements[0].getVal());
|
||||
CHECK (1+2 == elements[2].getID());
|
||||
CHECK (3+4+5 == elements[1].getID());
|
||||
CHECK (6+7+8+9 == elements[0].getID());
|
||||
|
||||
elements.clear();
|
||||
CHECK (EXPECT == cluster.numBytes());
|
||||
|
|
@ -540,8 +540,8 @@ namespace test{
|
|||
CHECK (sum(5) == Dummy::checksum());
|
||||
|
||||
CHECK (2 == elements.size());
|
||||
CHECK (1+2 == elements[1].getVal());
|
||||
CHECK (3+4+5 == elements[0].getVal());
|
||||
CHECK (1+2 == elements[1].getID());
|
||||
CHECK (3+4+5 == elements[0].getID());
|
||||
// note: this time the destructors will be invoked
|
||||
// from LinkedElements::clear(), but not from
|
||||
// the destructor of AllocationCluster
|
||||
|
|
|
|||
164
tests/lib/meta/fun-inline-test.cpp
Normal file
164
tests/lib/meta/fun-inline-test.cpp
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
FunInline(Test) - verify implementation of a functor with local delegate
|
||||
|
||||
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-inline-test.cpp
|
||||
** unit test \ref FunInline_test
|
||||
*/
|
||||
|
||||
|
||||
#include "test/run.hpp"
|
||||
#include "test/test-helper.hpp"
|
||||
#include "test/tracking-dummy.hpp"
|
||||
#include "lib/meta/fun-inline.hpp"
|
||||
#include "test/diagnostic-output.hpp"///////////TODO jaleck
|
||||
|
||||
using test::showType;
|
||||
using test::Dummy;
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta {
|
||||
namespace test {
|
||||
|
||||
namespace { // test fixture...
|
||||
|
||||
class TrackingFun
|
||||
: public Dummy
|
||||
{
|
||||
public:
|
||||
using Dummy::Dummy;
|
||||
|
||||
long
|
||||
operator() (int val)
|
||||
{
|
||||
return Dummy::calc(val);
|
||||
}
|
||||
};
|
||||
|
||||
}//(End) test fixture
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*************************************************************//**
|
||||
* @test Documentation of the defining structure for a functor
|
||||
* that manages a delegate within an opaque inline buffer.
|
||||
* - setup of the invocation delegation
|
||||
* @remark this design complements std::function, insofar the
|
||||
* wrapped functor implementation is stored within the
|
||||
* FunInline itself, never using any heap memory.
|
||||
* @todo 4/2026 unfinished design draft.
|
||||
* @see FunctionSignature_test
|
||||
*/
|
||||
class FunInline_test : public Test
|
||||
{
|
||||
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
seedRand();
|
||||
|
||||
simpleUsage();
|
||||
design_Invoker();
|
||||
design_BufferHandling();
|
||||
}
|
||||
|
||||
|
||||
/** @test example usage of an functor wrapper with local storage. */
|
||||
void
|
||||
simpleUsage()
|
||||
{
|
||||
TODO ("show simple usage of the front-end builder");
|
||||
}
|
||||
|
||||
|
||||
/** @test document the design of the Invoker
|
||||
*/
|
||||
void
|
||||
design_Invoker()
|
||||
{
|
||||
int tap{-1};
|
||||
auto funny = [&](int i){ return -(tap = i); };
|
||||
CHECK (-1 == tap);
|
||||
CHECK (-2 == funny(2));
|
||||
CHECK ( 2 == tap);
|
||||
|
||||
using Sig = long(int);
|
||||
using FunIn = FunInline<Sig, sizeof(TrackingFun)>;
|
||||
using Invoker = FunIn::Invoker<decltype(funny)>;
|
||||
|
||||
CHECK (showType<Invoker::Sig>() == "long (int)"_expect );
|
||||
CHECK (showType<Invoker::ISig>() == "long (void*, int&&)"_expect );
|
||||
|
||||
// The Invoker(Builder) actually defines a static function with that signature...
|
||||
using InvoSig = _Fun<decltype(Invoker::dispatch)>::Sig;
|
||||
CHECK (showType<InvoSig>() == "long (void*, int&&)"_expect );
|
||||
|
||||
// and, through magic machinations of the C++ platform,
|
||||
// this dispatcher function actually inlines the target functor...
|
||||
InvoSig* invoker = Invoker::dispatch;
|
||||
void* delegate = &funny;
|
||||
|
||||
CHECK (-55 == (*invoker) (delegate, 55) );
|
||||
CHECK ( 55 == tap);
|
||||
|
||||
// since this delegation through the invoker is an abstraction,
|
||||
// other functors with compatible signature can be invoked as well,
|
||||
// once a suitable Invoker was instantiated to generate dispatch code...
|
||||
TrackingFun dummy;
|
||||
invoker = FunIn::Invoker<TrackingFun>::dispatch;
|
||||
delegate = &dummy;
|
||||
|
||||
CHECK ((*invoker) (delegate, -55) == dummy.getID() - 55 );
|
||||
// Remark: Dummy::calc(arg) ≔ val + arg
|
||||
}
|
||||
|
||||
|
||||
/** @test verify proper instance handling
|
||||
* for the embedded functor delegate
|
||||
*/
|
||||
void
|
||||
design_BufferHandling()
|
||||
{
|
||||
using Sig = long(int);
|
||||
using FunIn = FunInline<Sig, sizeof(TrackingFun)>;
|
||||
|
||||
CHECK (0 == Dummy::checksum());
|
||||
{
|
||||
TrackingFun dummy;
|
||||
int id = dummy.getID();
|
||||
CHECK (id == Dummy::checksum());
|
||||
{
|
||||
FunIn inFun{dummy};
|
||||
CHECK (inFun);
|
||||
|
||||
CHECK (2*id == Dummy::checksum());
|
||||
|
||||
long res = inFun(42);
|
||||
CHECK (res == id+42);
|
||||
}
|
||||
CHECK (id == Dummy::checksum());
|
||||
}
|
||||
CHECK (0 == Dummy::checksum());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** Register this test class... */
|
||||
LAUNCHER (FunInline_test, "unit common");
|
||||
|
||||
|
||||
}}}// namespace lib::meta::test
|
||||
|
|
@ -47,7 +47,7 @@ namespace test{
|
|||
calc (int i)
|
||||
{
|
||||
if (!i)
|
||||
return getVal() + trigger_;
|
||||
return getID() + trigger_;
|
||||
else
|
||||
return Dummy::calc(i);
|
||||
}
|
||||
|
|
@ -57,7 +57,7 @@ namespace test{
|
|||
: Dummy(id)
|
||||
, trigger_(trigger)
|
||||
{
|
||||
if (trigger == getVal())
|
||||
if (trigger == getID())
|
||||
throw error::Fatal ("Subversive Bomb", LUMIERA_ERROR_SUBVERSIVE);
|
||||
}
|
||||
|
||||
|
|
@ -130,11 +130,11 @@ namespace test{
|
|||
CHECK (0 == Dummy::checksum());
|
||||
|
||||
container.populate();
|
||||
CHECK (Dummy::checksum() == container[0].getVal()
|
||||
+ container[1].getVal()
|
||||
+ container[2].getVal()
|
||||
+ container[3].getVal()
|
||||
+ container[4].getVal());
|
||||
CHECK (Dummy::checksum() == container[0].getID()
|
||||
+ container[1].getID()
|
||||
+ container[2].getID()
|
||||
+ container[3].getID()
|
||||
+ container[4].getID());
|
||||
}
|
||||
CHECK (0 == Dummy::checksum());
|
||||
}
|
||||
|
|
@ -153,7 +153,7 @@ namespace test{
|
|||
CollD::iterator ii = coll.begin();
|
||||
while (ii)
|
||||
{
|
||||
CHECK (check == ii->getVal());
|
||||
CHECK (check == ii->getID());
|
||||
CHECK (check == ii->calc(+5) - 5);
|
||||
++check;
|
||||
++ii;
|
||||
|
|
@ -166,7 +166,7 @@ namespace test{
|
|||
CollD::const_iterator cii = const_coll.begin();
|
||||
while (cii)
|
||||
{
|
||||
CHECK (check == cii->getVal());
|
||||
CHECK (check == cii->getID());
|
||||
++check;
|
||||
++cii;
|
||||
}
|
||||
|
|
@ -176,13 +176,13 @@ namespace test{
|
|||
check = 0;
|
||||
for (auto& entry : coll)
|
||||
{
|
||||
CHECK (check == entry.getVal());
|
||||
CHECK (check == entry.getID());
|
||||
++check;
|
||||
}
|
||||
check = 0;
|
||||
for (auto const& entry : const_coll)
|
||||
{
|
||||
CHECK (check == entry.getVal());
|
||||
CHECK (check == entry.getID());
|
||||
++check;
|
||||
}
|
||||
|
||||
|
|
@ -252,7 +252,7 @@ namespace test{
|
|||
CHECK (sum + rr == Dummy::checksum());
|
||||
|
||||
|
||||
CHECK (d0.calc(11) == coll[0].getVal() + 11 );
|
||||
CHECK (d0.calc(11) == coll[0].getID() + 11 );
|
||||
CHECK (d1.calc(22) == rr + 22);
|
||||
CHECK (d2.calc(33) == rr + 33);
|
||||
CHECK (d2.calc(0) == rr + (rr+1) ); // SubDummy's special implementation of the acc()-function
|
||||
|
|
@ -270,7 +270,7 @@ namespace test{
|
|||
CHECK (d0.calc(0) == 11 + 22);
|
||||
|
||||
// The others even point into obsoleted storage holding zombie objects
|
||||
CHECK (d1.getVal() == Dummy::DEAD);
|
||||
CHECK (d1.getID() == Dummy::DEAD);
|
||||
|
||||
}
|
||||
CHECK (0 == Dummy::checksum());
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ namespace test{
|
|||
VectD::iterator ii = holder.begin();
|
||||
while (ii)
|
||||
{
|
||||
CHECK (check == ii->getVal());
|
||||
CHECK (check == ii->getID());
|
||||
++check;
|
||||
++ii;
|
||||
}
|
||||
|
|
@ -118,7 +118,7 @@ namespace test{
|
|||
VectD::const_iterator cii = holder.begin();
|
||||
while (cii)
|
||||
{
|
||||
CHECK (check == cii->getVal());
|
||||
CHECK (check == cii->getID());
|
||||
++check;
|
||||
++cii;
|
||||
}
|
||||
|
|
@ -160,18 +160,18 @@ namespace test{
|
|||
CHECK (5 == holder.size());
|
||||
CHECK (0 < Dummy::checksum());
|
||||
|
||||
id2 = holder[2].getVal();
|
||||
id3 = holder[3].getVal();
|
||||
id2 = holder[2].getID();
|
||||
id3 = holder[3].getID();
|
||||
|
||||
extracted = holder.detach(& holder[2]);
|
||||
CHECK (id2 == extracted->getVal());
|
||||
CHECK (id3 == holder[2].getVal());
|
||||
CHECK (id2 == extracted->getID());
|
||||
CHECK (id3 == holder[2].getID());
|
||||
CHECK (4 == holder.size());
|
||||
}
|
||||
CHECK (0 < Dummy::checksum()); // not all dummies are dead
|
||||
CHECK (id2 == Dummy::checksum()); // #2 is alive!
|
||||
|
||||
extracted->setVal(id2+id3);
|
||||
extracted->setID (id2+id3);
|
||||
CHECK (id2+id3 == Dummy::checksum());
|
||||
|
||||
delete extracted;
|
||||
|
|
@ -194,9 +194,9 @@ namespace test{
|
|||
CHECK ( isnil (left));
|
||||
auto sum = Dummy::checksum();
|
||||
CHECK (sum > 0);
|
||||
int id0 = org[0].getVal(),
|
||||
id1 = org[1].getVal(),
|
||||
id2 = org[2].getVal();
|
||||
int id0 = org[0].getID(),
|
||||
id1 = org[1].getID(),
|
||||
id2 = org[2].getID();
|
||||
|
||||
// create by move
|
||||
VectD right{std::move (org)};
|
||||
|
|
@ -211,9 +211,9 @@ namespace test{
|
|||
CHECK (not isnil (left));
|
||||
CHECK ( isnil (right));
|
||||
CHECK (sum == Dummy::checksum());
|
||||
CHECK (id0 == left[0].getVal());
|
||||
CHECK (id1 == left[1].getVal());
|
||||
CHECK (id2 == left[2].getVal());
|
||||
CHECK (id0 == left[0].getID());
|
||||
CHECK (id1 == left[1].getID());
|
||||
CHECK (id2 == left[2].getID());
|
||||
|
||||
}
|
||||
CHECK (0 == Dummy::checksum());
|
||||
|
|
|
|||
|
|
@ -72,11 +72,11 @@ namespace test{
|
|||
: Dummy(seed)
|
||||
{
|
||||
ext_.fill(seed);
|
||||
setVal ((i+1)*seed);
|
||||
setID ((i+1)*seed);
|
||||
}
|
||||
~Num()
|
||||
{
|
||||
setVal (getVal() - explore(ext_).resultSum());
|
||||
setID (getID() - explore(ext_).resultSum());
|
||||
}
|
||||
|
||||
long
|
||||
|
|
@ -193,7 +193,7 @@ namespace test{
|
|||
builder.emplace<Num<3>>()
|
||||
.emplace<Num<2>>(1);
|
||||
CHECK (2 == builder.size()); // use information functions...
|
||||
CHECK (3 == builder[1].getVal()); // to peek into contents assembled thus far...
|
||||
CHECK (3 == builder[1].getID()); // to peek into contents assembled thus far...
|
||||
VERIFY_ERROR (INDEX_BOUNDS, builder[2] ); // runtime bounds check on the builder (but not on the product!)
|
||||
builder.fillElm(2);
|
||||
CHECK (4 == builder.size());
|
||||
|
|
@ -204,16 +204,16 @@ namespace test{
|
|||
CHECK ( isnil(builder));
|
||||
CHECK (not isnil(elms));
|
||||
CHECK (7 == elms.size());
|
||||
CHECK (elms[0].getVal() == (3+1)*3); // indeed a Num<3> with default-seed ≡ 3
|
||||
CHECK (elms[0].calc(1) == 3 + 1 + (3+3+3)); // indeed called the overridden calc() operation
|
||||
CHECK (elms[1].getVal() == (2+1)*1); // indeed a Num<2> with seed ≡ 1
|
||||
CHECK (elms[1].calc(1) == 2 + 1 + (1+1)); // indeed the overridden calc() picking from the Array(1,1)
|
||||
CHECK (isLimited (1, elms[2].getVal(), 100'000'000)); // indeed a Dummy with default random seed
|
||||
CHECK (isLimited (1, elms[3].getVal(), 100'000'000)); // and this one too, since we filled in two instances
|
||||
CHECK (elms[4].getVal() == 5); // followed by tree instances Dummy(5)
|
||||
CHECK (elms[5].getVal() == 5);
|
||||
CHECK (elms[6].getVal() == 5);
|
||||
CHECK (elms[6].calc(1) == 5+1); // indeed invoking the base implementation of calc()
|
||||
CHECK (elms[0].getID() == (3+1)*3); // indeed a Num<3> with default-seed ≡ 3
|
||||
CHECK (elms[0].calc(1) == 3 + 1 + (3+3+3)); // indeed called the overridden calc() operation
|
||||
CHECK (elms[1].getID() == (2+1)*1); // indeed a Num<2> with seed ≡ 1
|
||||
CHECK (elms[1].calc(1) == 2 + 1 + (1+1)); // indeed the overridden calc() picking from the Array(1,1)
|
||||
CHECK (isLimited (1, elms[2].getID(), 100'000'000)); // indeed a Dummy with default random seed
|
||||
CHECK (isLimited (1, elms[3].getID(), 100'000'000)); // and this one too, since we filled in two instances
|
||||
CHECK (elms[4].getID() == 5); // followed by tree instances Dummy(5)
|
||||
CHECK (elms[5].getID() == 5);
|
||||
CHECK (elms[6].getID() == 5);
|
||||
CHECK (elms[6].calc(1) == 5+1); // indeed invoking the base implementation of calc()
|
||||
}
|
||||
|
||||
{ // Scenario-2 : unrelated element types
|
||||
|
|
@ -656,11 +656,11 @@ namespace test{
|
|||
|
||||
elms = builder.build(); // Note: assigning to the existing front-end (which is storage agnostic)
|
||||
CHECK (5 == elms.size());
|
||||
CHECK (23 == elms.back().getVal());
|
||||
CHECK (55 == extraDummy.getVal());
|
||||
CHECK (23 == elms.back().getID());
|
||||
CHECK (55 == extraDummy.getID());
|
||||
} // Now the Builder and the ExtraDummy is gone...
|
||||
CHECK (5 == elms.size()); // while all created elements are still there, sitting in the Allocationcluster
|
||||
CHECK (23 == elms.back().getVal());
|
||||
CHECK (23 == elms.back().getID());
|
||||
CHECK (2 == clu.numExtents());
|
||||
CHECK (clu.numBytes() == allotted);
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ namespace test{
|
|||
void
|
||||
doIt (int extra)
|
||||
{
|
||||
watcher.setVal (extra);
|
||||
watcher.setID (extra);
|
||||
sleep_for (5ms);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -117,24 +117,24 @@ namespace test{
|
|||
CHECK (Dummy::checksum() == 0);
|
||||
{
|
||||
Dummy dum1; // picks a random positive int by default...
|
||||
CHECK (0 < dum1.getVal() and dum1.getVal() <= 100'000'000);
|
||||
CHECK (Dummy::checksum() == dum1.getVal());
|
||||
CHECK (0 < dum1.getID() and dum1.getID() <= 100'000'000);
|
||||
CHECK (Dummy::checksum() == dum1.getID());
|
||||
|
||||
Dummy dum2{55};
|
||||
CHECK (dum2.getVal() == 55);
|
||||
CHECK (Dummy::checksum() == dum1.getVal() + 55);
|
||||
CHECK (dum2.getID() == 55);
|
||||
CHECK (Dummy::checksum() == dum1.getID() + 55);
|
||||
|
||||
Dummy dum3{move (dum2)};
|
||||
CHECK (dum3.getVal() == 55);
|
||||
CHECK (dum2.getVal() == Dummy::DEFUNCT);
|
||||
CHECK (dum3.getID() == 55);
|
||||
CHECK (dum2.getID() == Dummy::DEFUNCT);
|
||||
|
||||
dum3.setVal (23);
|
||||
CHECK (dum3.getVal() == 23);
|
||||
dum3.setID (23);
|
||||
CHECK (dum3.getID() == 23);
|
||||
|
||||
dum1 = move (dum3);
|
||||
CHECK (dum1.getVal() == 23 );
|
||||
CHECK (dum2.getVal() == Dummy::DEFUNCT);
|
||||
CHECK (dum3.getVal() == Dummy::DEFUNCT);
|
||||
CHECK (dum1.getID() == 23 );
|
||||
CHECK (dum2.getID() == Dummy::DEFUNCT);
|
||||
CHECK (dum3.getID() == Dummy::DEFUNCT);
|
||||
CHECK (Dummy::checksum() == 23 );
|
||||
|
||||
Dummy::activateCtorFailure (true);
|
||||
|
|
@ -148,9 +148,9 @@ namespace test{
|
|||
Dummy::checksum() -= v;
|
||||
}
|
||||
Dummy::activateCtorFailure (false);
|
||||
CHECK (dum1.getVal() == 23 );
|
||||
CHECK (dum2.getVal() == Dummy::DEFUNCT);
|
||||
CHECK (dum3.getVal() == Dummy::DEFUNCT);
|
||||
CHECK (dum1.getID() == 23 );
|
||||
CHECK (dum2.getID() == Dummy::DEFUNCT);
|
||||
CHECK (dum3.getID() == Dummy::DEFUNCT);
|
||||
CHECK (Dummy::checksum() == 23 );
|
||||
}
|
||||
CHECK (Dummy::checksum() == 0);
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ namespace test {
|
|||
* - verify the front-end can be copied without impact on the JobFunctor
|
||||
* - verify the heap allocated functor keeps itself alive
|
||||
* even when the front-end handle is already gone.
|
||||
* - verify the functor de-allocates itself after latst invocation
|
||||
* - verify the functor de-allocates itself after last invocation
|
||||
* @todo WIP 12/23 ✔ define ⟶ ✔ implement
|
||||
*/
|
||||
void
|
||||
|
|
@ -111,7 +111,7 @@ namespace test {
|
|||
(JobParameter param) mutable
|
||||
{
|
||||
int mark = param.invoKey.part.a;
|
||||
tracker.setVal (mark);
|
||||
tracker.setID (mark);
|
||||
}}; // △△△ invocation should alter checksum
|
||||
|
||||
// one Dummy instance was implanted
|
||||
|
|
@ -134,7 +134,7 @@ namespace test {
|
|||
(JobParameter param) mutable
|
||||
{
|
||||
int mark = param.invoKey.part.a;
|
||||
tracker.setVal (mark);
|
||||
tracker.setID (mark);
|
||||
};
|
||||
CHECK (23 == Dummy::checksum());
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue