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:
Fischlurch 2026-04-07 23:28:44 +02:00
parent 0760525ea5
commit a0d83b4fe8
15 changed files with 995 additions and 244 deletions

View file

@ -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
View 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_*/

View file

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

View file

@ -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&

View file

@ -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

View file

@ -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());

View file

@ -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

View 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

View file

@ -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());

View file

@ -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());

View file

@ -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);

View file

@ -79,7 +79,7 @@ namespace test{
void
doIt (int extra)
{
watcher.setVal (extra);
watcher.setID (extra);
sleep_for (5ms);
}
};

View file

@ -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);

View file

@ -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