2023-11-23 23:42:55 +01:00
|
|
|
|
/*
|
|
|
|
|
|
LazyInit(Test) - verify a mechanism to install a self-initialising functor
|
|
|
|
|
|
|
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
|
|
|
|
Copyright (C)
|
|
|
|
|
|
2023, Hermann Vosseler <Ichthyostega@web.de>
|
2023-11-23 23:42:55 +01:00
|
|
|
|
|
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
|
|
|
|
**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.
|
2023-11-23 23:42:55 +01:00
|
|
|
|
|
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
|
|
|
|
* *****************************************************************/
|
2023-11-23 23:42:55 +01:00
|
|
|
|
|
|
|
|
|
|
/** @file lazy-init-test.cpp
|
|
|
|
|
|
** unit test \ref LazyInit_test
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "lib/test/run.hpp"
|
2023-11-25 23:47:20 +01:00
|
|
|
|
#include "lib/test/test-helper.hpp"
|
2023-11-23 23:42:55 +01:00
|
|
|
|
#include "lib/lazy-init.hpp"
|
2023-11-25 23:47:20 +01:00
|
|
|
|
#include "lib/meta/util.hpp"
|
2023-11-24 04:15:15 +01:00
|
|
|
|
#include "lib/util.hpp"
|
2023-11-23 23:42:55 +01:00
|
|
|
|
|
2023-11-25 03:36:19 +01:00
|
|
|
|
#include <memory>
|
2023-11-23 23:42:55 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace lib {
|
|
|
|
|
|
namespace test{
|
|
|
|
|
|
|
2023-11-24 04:15:15 +01:00
|
|
|
|
using util::isSameObject;
|
2023-11-24 03:17:27 +01:00
|
|
|
|
using lib::meta::isFunMember;
|
2023-11-26 03:04:59 +01:00
|
|
|
|
using lib::meta::disable_if_self;
|
2023-11-25 03:36:19 +01:00
|
|
|
|
using err::LUMIERA_ERROR_LIFECYCLE;
|
2023-11-25 23:47:20 +01:00
|
|
|
|
using std::make_unique;
|
2023-11-23 23:42:55 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************************************//**
|
|
|
|
|
|
* @test Verify a mix-in to allow for lazy initialisation of complex infrastructure
|
|
|
|
|
|
* tied to a std::function; the intention is to have a »trap« hidden in the
|
|
|
|
|
|
* function itself to trigger on first use and perform the one-time
|
2023-11-25 23:47:20 +01:00
|
|
|
|
* initialisation, then finally lock the object at a fixed place.
|
2023-11-23 23:42:55 +01:00
|
|
|
|
* @see lazy-init.hpp
|
|
|
|
|
|
* @see lib::RandomDraw
|
|
|
|
|
|
*/
|
|
|
|
|
|
class LazyInit_test
|
|
|
|
|
|
: public Test
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
run (Arg)
|
|
|
|
|
|
{
|
2024-11-13 02:23:23 +01:00
|
|
|
|
seedRand();
|
|
|
|
|
|
|
2023-11-24 03:17:27 +01:00
|
|
|
|
verify_trojanLambda();
|
2023-11-24 04:15:15 +01:00
|
|
|
|
verify_inlineStorage();
|
2023-11-24 23:26:41 +01:00
|
|
|
|
verify_TargetRelocation();
|
|
|
|
|
|
verify_triggerMechanism();
|
|
|
|
|
|
verify_lazyInitialisation();
|
2023-11-25 03:36:19 +01:00
|
|
|
|
verify_complexUsageWithCopy();
|
2023-11-23 23:42:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-11-24 03:17:27 +01:00
|
|
|
|
/** @test verify construction of the »trap« front-end eventually to trigger initialisation
|
|
|
|
|
|
* - this test does not involve any std::function, rather a heap-allocated copy of a λ
|
|
|
|
|
|
* # the _target function_ finally to be invoked performs a verifiable computation
|
|
|
|
|
|
* # the _delegate_ receives an memory location and returns a reference to the target
|
|
|
|
|
|
* # the generated _»trojan λ«_ captures its own address, invokes the delegate,
|
2023-11-25 03:36:19 +01:00
|
|
|
|
* retrieves a reference to a target functor, and finally invokes this with actual arguments.
|
2023-11-24 03:17:27 +01:00
|
|
|
|
* @remark the purpose of this convoluted scheme is for the _delegate to perform initialisation,_
|
|
|
|
|
|
* taking into account the current memory location „sniffed“ by the trojan.
|
|
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
verify_trojanLambda()
|
|
|
|
|
|
{
|
|
|
|
|
|
size_t beacon;
|
|
|
|
|
|
auto fun = [&](uint challenge){ return beacon+challenge; };
|
|
|
|
|
|
|
|
|
|
|
|
using Sig = size_t(uint);
|
|
|
|
|
|
CHECK (isFunMember<Sig> (&fun));
|
|
|
|
|
|
|
2024-11-13 02:23:23 +01:00
|
|
|
|
beacon = rani();
|
2023-11-24 03:17:27 +01:00
|
|
|
|
uint c = beacon % 42;
|
|
|
|
|
|
// verify we can invoke the target function
|
|
|
|
|
|
CHECK (beacon+c == fun(c));
|
|
|
|
|
|
|
|
|
|
|
|
// verify we can also invoke the target function through a reference
|
|
|
|
|
|
using FunType = decltype(fun);
|
|
|
|
|
|
FunType& funRef = fun;
|
|
|
|
|
|
CHECK (beacon+c == funRef(c));
|
|
|
|
|
|
|
|
|
|
|
|
// construct delegate function exposing the expected behaviour;
|
|
|
|
|
|
// additionally this function captures the passed-in address.
|
|
|
|
|
|
RawAddr location{nullptr};
|
|
|
|
|
|
auto delegate = [&](RawAddr adr) -> FunType&
|
|
|
|
|
|
{
|
|
|
|
|
|
location = adr;
|
|
|
|
|
|
return fun;
|
|
|
|
|
|
};
|
|
|
|
|
|
using Delegate = decltype(delegate);
|
2023-11-25 03:36:19 +01:00
|
|
|
|
auto delP = make_unique<Delegate> (delegate);
|
2023-11-24 03:17:27 +01:00
|
|
|
|
|
|
|
|
|
|
// verify the heap-allocated copy of the delegate behaves as expected
|
|
|
|
|
|
location = nullptr;
|
|
|
|
|
|
CHECK (beacon+c == (*delP)(this)(c));
|
|
|
|
|
|
CHECK (location == this);
|
|
|
|
|
|
|
2023-11-25 03:36:19 +01:00
|
|
|
|
// now (finally) build the »trap function«...
|
|
|
|
|
|
auto trojanLambda = TrojanFun<Sig>::generateTrap (delP.get());
|
2023-11-24 15:43:26 +01:00
|
|
|
|
CHECK (sizeof(trojanLambda) == sizeof(size_t));
|
2023-11-24 03:17:27 +01:00
|
|
|
|
|
|
|
|
|
|
// on invocation...
|
|
|
|
|
|
// - it captures its current location
|
|
|
|
|
|
// - passes this to the delegate
|
|
|
|
|
|
// - invokes the target function returned from the delegate
|
|
|
|
|
|
CHECK (beacon+c == trojanLambda(c));
|
|
|
|
|
|
CHECK (location == &trojanLambda);
|
|
|
|
|
|
|
2023-11-25 03:36:19 +01:00
|
|
|
|
// repeat same with a copy, and changed beacon value
|
2023-11-24 03:17:27 +01:00
|
|
|
|
auto trojanClone = trojanLambda;
|
2024-11-13 02:23:23 +01:00
|
|
|
|
beacon = rani();
|
2023-11-24 03:17:27 +01:00
|
|
|
|
c = beacon % 55;
|
|
|
|
|
|
CHECK (beacon+c == trojanClone(c));
|
|
|
|
|
|
CHECK (location == &trojanClone);
|
|
|
|
|
|
CHECK (beacon+c == trojanLambda(c));
|
|
|
|
|
|
CHECK (location == &trojanLambda);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-11-25 03:36:19 +01:00
|
|
|
|
/** @test verify that std::function indeed stores a simple functor inline.
|
2023-11-24 15:43:26 +01:00
|
|
|
|
* @remark The implementation of LazyInit relies crucially on a known optimisation
|
|
|
|
|
|
* in the standard library ─ which unfortunately is not guaranteed by the standard:
|
|
|
|
|
|
* Typically, std::function will apply _small object optimisation_ to place a very
|
|
|
|
|
|
* small functor directly into the wrapper, if the payload has a trivial copy-ctor.
|
2023-11-25 03:36:19 +01:00
|
|
|
|
* `Libstdc++` is known to be rather restrictive, while other implementations trade
|
|
|
|
|
|
* increased storage size of std::function against more optimisation possibilities.
|
2023-11-24 15:43:26 +01:00
|
|
|
|
* LazyInit exploits this optimisation to „spy“ about the current object location,
|
2023-11-25 03:36:19 +01:00
|
|
|
|
* allowing to execute the lazy initialisation on first use, without further help
|
2023-11-24 15:43:26 +01:00
|
|
|
|
* by client code. This trickery seems to be the only way, since λ-capture by reference
|
2023-11-25 03:36:19 +01:00
|
|
|
|
* is broken after copying or moving the host object (typically required for DSL use).
|
2023-11-24 15:43:26 +01:00
|
|
|
|
* In case this turns out to be fragile, LazyInit should become a "LateInit" and needs
|
|
|
|
|
|
* help by the client or the user to trigger initialisation; alternatively the DSL
|
|
|
|
|
|
* could be split off into a separate builder object distinct from RandomDraw.
|
2023-11-23 23:42:55 +01:00
|
|
|
|
*/
|
|
|
|
|
|
void
|
2023-11-24 04:15:15 +01:00
|
|
|
|
verify_inlineStorage()
|
2023-11-23 23:42:55 +01:00
|
|
|
|
{
|
2023-11-24 15:43:26 +01:00
|
|
|
|
// char payload[24];// ◁─────────────────────────────── use this to make the test fail....
|
2025-07-02 22:18:39 +02:00
|
|
|
|
const char* payload = "I am innocent as a lamb";
|
2023-11-24 15:43:26 +01:00
|
|
|
|
auto lambda = [payload]{ return RawAddr(&payload); };
|
2023-11-24 04:15:15 +01:00
|
|
|
|
|
2023-11-24 15:43:26 +01:00
|
|
|
|
RawAddr location = lambda();
|
|
|
|
|
|
CHECK (location == &lambda);
|
|
|
|
|
|
|
|
|
|
|
|
std::function funWrap{lambda};
|
|
|
|
|
|
CHECK (funWrap);
|
|
|
|
|
|
CHECK (not isSameObject (funWrap, lambda));
|
|
|
|
|
|
|
|
|
|
|
|
location = funWrap();
|
|
|
|
|
|
CHECK (util::isCloseBy (location, funWrap));
|
|
|
|
|
|
// if »small object optimisation« was used,
|
|
|
|
|
|
// the lambda will be copied directly into the std:function;
|
|
|
|
|
|
// otherwise it will be heap allocated and this test fails.
|
|
|
|
|
|
|
|
|
|
|
|
// for context: these are considered "close by",
|
|
|
|
|
|
// since both are sitting right here in the same stack frame
|
|
|
|
|
|
CHECK (util::isCloseBy (funWrap, lambda));
|
2023-11-23 23:42:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-11-24 23:26:41 +01:00
|
|
|
|
/** @test verify navigating an object structure
|
|
|
|
|
|
* by applying known offsets consecutively
|
|
|
|
|
|
* from a starting point within an remote instance
|
|
|
|
|
|
* @remark in the real usage scenario, we know _only_ the offset
|
2023-11-25 03:36:19 +01:00
|
|
|
|
* and attempt to find home without knowing the layout.
|
2023-11-24 23:26:41 +01:00
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
verify_TargetRelocation()
|
|
|
|
|
|
{
|
|
|
|
|
|
struct Nested
|
|
|
|
|
|
{
|
2024-11-13 02:23:23 +01:00
|
|
|
|
int unrelated{rani()};
|
|
|
|
|
|
int anchor{rani()};
|
2023-11-24 23:26:41 +01:00
|
|
|
|
};
|
|
|
|
|
|
struct Demo
|
|
|
|
|
|
{
|
|
|
|
|
|
Nested nested;
|
|
|
|
|
|
virtual ~Demo(){ };
|
|
|
|
|
|
virtual RawAddr peek()
|
|
|
|
|
|
{
|
|
|
|
|
|
return &nested.anchor;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// find out generic offset...
|
|
|
|
|
|
const ptrdiff_t offNested = []{
|
|
|
|
|
|
Nested probe;
|
|
|
|
|
|
return captureRawAddrOffset(&probe, &probe.anchor);
|
|
|
|
|
|
}();
|
|
|
|
|
|
Demo here;
|
|
|
|
|
|
// find out actual offset in existing object
|
|
|
|
|
|
const ptrdiff_t offBase = captureRawAddrOffset(&here, &here.nested);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (offBase > 0);
|
|
|
|
|
|
CHECK (offNested > 0);
|
|
|
|
|
|
|
|
|
|
|
|
// create a copy far far away...
|
2023-11-25 03:36:19 +01:00
|
|
|
|
auto farAway = make_unique<Demo> (here);
|
2023-11-24 23:26:41 +01:00
|
|
|
|
|
|
|
|
|
|
// reconstruct base address from starting point
|
|
|
|
|
|
RawAddr startPoint = farAway->peek();
|
|
|
|
|
|
Nested* farNested = relocate<Nested>(startPoint, -offNested);
|
|
|
|
|
|
CHECK (here.nested.unrelated == farNested->unrelated);
|
|
|
|
|
|
|
|
|
|
|
|
Demo* farSelf = relocate<Demo> (farNested, -offBase);
|
|
|
|
|
|
CHECK (here.nested.anchor == farSelf->nested.anchor);
|
|
|
|
|
|
CHECK (isSameObject (*farSelf, *farAway));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @test demonstrate the trigger mechanism in isolation
|
|
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
verify_triggerMechanism()
|
|
|
|
|
|
{
|
|
|
|
|
|
using Fun = std::function<float(int)>;
|
|
|
|
|
|
Fun theFun;
|
|
|
|
|
|
CHECK (not theFun);
|
|
|
|
|
|
|
|
|
|
|
|
int report{0};
|
|
|
|
|
|
auto delegate = [&report](RawAddr insideFun) -> Fun&
|
|
|
|
|
|
{
|
|
|
|
|
|
auto realFun = [&report](int num)
|
|
|
|
|
|
{
|
|
|
|
|
|
report += num;
|
|
|
|
|
|
return num + 23.55f;
|
|
|
|
|
|
};
|
|
|
|
|
|
Fun& target = *relocate<Fun>(insideFun, -FUNCTOR_PAYLOAD_OFFSET);
|
|
|
|
|
|
report = -42; // as proof that the init-delegate was invoked
|
|
|
|
|
|
target = realFun;
|
|
|
|
|
|
return target;
|
|
|
|
|
|
};
|
|
|
|
|
|
CHECK (not theFun);
|
|
|
|
|
|
// install the init-»trap«
|
|
|
|
|
|
theFun = TrojanFun<float(int)>::generateTrap (&delegate);
|
|
|
|
|
|
CHECK (theFun);
|
|
|
|
|
|
CHECK (0 == report);
|
|
|
|
|
|
|
|
|
|
|
|
// invoke function
|
2024-11-13 02:23:23 +01:00
|
|
|
|
int feed{1 + rani (100)};
|
2023-11-24 23:26:41 +01:00
|
|
|
|
float res = theFun (feed);
|
|
|
|
|
|
|
|
|
|
|
|
// delegate *and* realFun were invoked
|
|
|
|
|
|
CHECK (feed == report + 42);
|
|
|
|
|
|
CHECK (res = feed -42 +23.55f);
|
|
|
|
|
|
|
|
|
|
|
|
// again...
|
|
|
|
|
|
report = 0;
|
2024-11-13 02:23:23 +01:00
|
|
|
|
feed = -1-rani(20);
|
2023-11-24 23:26:41 +01:00
|
|
|
|
res = theFun (feed);
|
|
|
|
|
|
|
|
|
|
|
|
// this time the delegate was *not* invoked,
|
|
|
|
|
|
// only the installed realFun
|
|
|
|
|
|
CHECK (feed == report);
|
|
|
|
|
|
CHECK (res = feed + 23.55f);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @test demonstrate a basic usage scenario
|
|
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
verify_lazyInitialisation()
|
|
|
|
|
|
{
|
2023-11-25 01:09:37 +01:00
|
|
|
|
using Fun = std::function<float(int)>;
|
|
|
|
|
|
using Lazy = LazyInit<Fun>;
|
|
|
|
|
|
|
|
|
|
|
|
bool init{false};
|
|
|
|
|
|
uint invoked{0};
|
|
|
|
|
|
Lazy funny{funny, [&](Lazy* self)
|
|
|
|
|
|
{
|
|
|
|
|
|
Fun& thisFun = static_cast<Fun&> (*self);
|
|
|
|
|
|
|
|
|
|
|
|
thisFun = [&invoked](int num)
|
|
|
|
|
|
{
|
|
|
|
|
|
++invoked;
|
|
|
|
|
|
return num * 0.555f;
|
|
|
|
|
|
};
|
|
|
|
|
|
init = true;
|
|
|
|
|
|
}};
|
|
|
|
|
|
CHECK (not invoked);
|
|
|
|
|
|
CHECK (not init);
|
|
|
|
|
|
CHECK (funny);
|
|
|
|
|
|
|
2024-11-13 02:23:23 +01:00
|
|
|
|
int feed = 1 + rani(99);
|
2023-11-25 01:09:37 +01:00
|
|
|
|
CHECK (feed*0.555f == funny(feed));
|
|
|
|
|
|
CHECK (1 == invoked);
|
|
|
|
|
|
CHECK (init);
|
2023-11-24 23:26:41 +01:00
|
|
|
|
}
|
2023-11-25 03:36:19 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** elaborate setup used for integration test */
|
|
|
|
|
|
struct LazyDemo
|
|
|
|
|
|
: LazyInit<>
|
|
|
|
|
|
{
|
|
|
|
|
|
using Fun = std::function<int(int)>;
|
|
|
|
|
|
|
|
|
|
|
|
int seed{0};
|
|
|
|
|
|
Fun fun; // ◁────────────────────────────────── this will be initialised lazily....
|
|
|
|
|
|
|
|
|
|
|
|
template<typename FUN>
|
|
|
|
|
|
auto
|
|
|
|
|
|
buildInit (FUN&& fun2install)
|
|
|
|
|
|
{
|
|
|
|
|
|
return [theFun = forward<FUN> (fun2install)]
|
|
|
|
|
|
(LazyDemo* self)
|
2023-11-25 19:22:10 +01:00
|
|
|
|
{ // this runs when init is actually performed....
|
2023-11-25 03:36:19 +01:00
|
|
|
|
CHECK (self);
|
2023-11-25 19:22:10 +01:00
|
|
|
|
if (self->fun)
|
|
|
|
|
|
// chain-up behind existing function
|
2023-11-25 23:47:20 +01:00
|
|
|
|
self->fun = [self, prevFun=self->fun, nextFun=theFun]
|
2023-11-25 19:22:10 +01:00
|
|
|
|
(int i)
|
|
|
|
|
|
{
|
|
|
|
|
|
return nextFun (prevFun (i));
|
|
|
|
|
|
};
|
|
|
|
|
|
else
|
|
|
|
|
|
// build new function chain, inject seed from object
|
2023-11-25 23:47:20 +01:00
|
|
|
|
self->fun = [self, newFun=theFun]
|
2023-11-25 19:22:10 +01:00
|
|
|
|
(int i)
|
|
|
|
|
|
{
|
|
|
|
|
|
return newFun (i + self->seed); // Note: binding to actual instance location
|
|
|
|
|
|
};
|
2023-11-25 03:36:19 +01:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LazyDemo()
|
|
|
|
|
|
: LazyInit{MarkDisabled()}
|
|
|
|
|
|
, fun{}
|
|
|
|
|
|
{
|
|
|
|
|
|
installInitialiser(fun, buildInit([](int){ return 0; }));
|
|
|
|
|
|
}
|
2023-11-25 23:47:20 +01:00
|
|
|
|
// prevent this ctor from shadowing the copy ctors //////TICKET #963
|
2024-04-01 19:40:19 +02:00
|
|
|
|
template<typename FUN, typename =disable_if_self<LazyDemo, FUN>>
|
2023-11-25 19:22:10 +01:00
|
|
|
|
LazyDemo (FUN&& someFun)
|
2023-11-25 03:36:19 +01:00
|
|
|
|
: LazyInit{MarkDisabled()}
|
|
|
|
|
|
, fun{}
|
|
|
|
|
|
{
|
|
|
|
|
|
installInitialiser(fun, buildInit (forward<FUN> (someFun)));
|
|
|
|
|
|
}
|
2023-11-25 19:22:10 +01:00
|
|
|
|
|
|
|
|
|
|
template<typename FUN>
|
|
|
|
|
|
LazyDemo&&
|
|
|
|
|
|
attach (FUN&& someFun)
|
|
|
|
|
|
{
|
|
|
|
|
|
installInitialiser(fun, buildInit (forward<FUN> (someFun)));
|
|
|
|
|
|
return move(*this);
|
|
|
|
|
|
}
|
2023-11-25 03:36:19 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @test use an elaborately constructed example to cover more corner cases
|
|
|
|
|
|
* - the function to manage and initialise lazily is _a member_ of the _derived class_
|
|
|
|
|
|
* - the initialisation routine _adapts_ this function and links it with the current
|
|
|
|
|
|
* object location; thus, invoking this function on a copy would crash / corrupt memory.
|
|
|
|
|
|
* - however, as long as initialisation has not been triggered, LazyDemo instances can be
|
2023-11-25 19:22:10 +01:00
|
|
|
|
* copied; they may even be assigned to existing instances, overwriting their state.
|
|
|
|
|
|
* - a second given function will be chained behind the first one; this happens immediately
|
|
|
|
|
|
* if the first function was already invoked (and this initialised)
|
2023-11-25 23:47:20 +01:00
|
|
|
|
* - but when however both functions are attached immediately, prior to invocation,
|
|
|
|
|
|
* then an elaborate chain of initialisers is setup behind the scenes and played back
|
|
|
|
|
|
* in definition order once lazy initialisation is triggered
|
|
|
|
|
|
* - all the intermediary state is safe to copy and move and fork
|
|
|
|
|
|
* @remark 11/2023 memory allocations were verified using lib::test::Tracker and the EventLog
|
2023-11-25 03:36:19 +01:00
|
|
|
|
*/
|
|
|
|
|
|
void
|
|
|
|
|
|
verify_complexUsageWithCopy()
|
|
|
|
|
|
{
|
2023-11-25 23:47:20 +01:00
|
|
|
|
LazyDemo dd;
|
|
|
|
|
|
CHECK (not dd.isInit()); // not initialised, since function was not invoked yet
|
|
|
|
|
|
CHECK (dd.fun); // the functor is not empty anymore, since the »trap« was installed
|
2023-11-25 03:36:19 +01:00
|
|
|
|
|
2023-11-25 23:47:20 +01:00
|
|
|
|
dd.seed = 2;
|
|
|
|
|
|
CHECK (0 == dd.fun(22)); // d1 was default initialised and thus got the "return 0" function
|
|
|
|
|
|
CHECK (dd.isInit()); // first invocation also triggered the init-routine
|
2023-11-25 03:36:19 +01:00
|
|
|
|
|
|
|
|
|
|
// is »engaged« after init and rejects move / copy
|
2023-11-25 23:47:20 +01:00
|
|
|
|
VERIFY_ERROR (LIFECYCLE, LazyDemo dx{move(dd)} );
|
2023-11-25 03:36:19 +01:00
|
|
|
|
|
|
|
|
|
|
|
2023-11-25 23:47:20 +01:00
|
|
|
|
dd = LazyDemo{[](int i) // assign a fresh copy (discarding any state in d1)
|
2023-11-25 03:36:19 +01:00
|
|
|
|
{
|
|
|
|
|
|
return i + 1; // using a "return i+1" function
|
|
|
|
|
|
}};
|
2023-11-25 23:47:20 +01:00
|
|
|
|
CHECK (not dd.isInit());
|
|
|
|
|
|
CHECK (dd.seed == 0); // assignment indeed erased any existing settings (seed≔2)
|
|
|
|
|
|
CHECK (dd.fun);
|
2023-11-25 03:36:19 +01:00
|
|
|
|
|
2023-11-25 23:47:20 +01:00
|
|
|
|
CHECK (23 == dd.fun(22)); // new function was tied in (while also referring to self->seed)
|
|
|
|
|
|
CHECK (dd.isInit());
|
|
|
|
|
|
dd.seed = 3; // set the seed
|
|
|
|
|
|
CHECK (26 == dd.fun(22)); // seed value is picked up dynamically
|
2023-11-25 03:36:19 +01:00
|
|
|
|
|
2023-11-25 23:47:20 +01:00
|
|
|
|
VERIFY_ERROR (LIFECYCLE, LazyDemo dx{move(dd)} );
|
2023-11-25 19:22:10 +01:00
|
|
|
|
|
|
|
|
|
|
// attach a further function, to be chained-up
|
2023-11-25 23:47:20 +01:00
|
|
|
|
dd.attach([](int i)
|
2023-11-25 19:22:10 +01:00
|
|
|
|
{
|
|
|
|
|
|
return i / 2;
|
|
|
|
|
|
});
|
2023-11-25 23:47:20 +01:00
|
|
|
|
CHECK (dd.isInit());
|
|
|
|
|
|
CHECK (dd.seed == 3);
|
|
|
|
|
|
CHECK (12 == dd.fun(21)); // 21+3+1=25 / 2
|
|
|
|
|
|
CHECK (13 == dd.fun(22));
|
|
|
|
|
|
CHECK (13 == dd.fun(23));
|
|
|
|
|
|
dd.seed++;
|
|
|
|
|
|
CHECK (14 == dd.fun(23)); // 23+4+1=28 / 2
|
|
|
|
|
|
CHECK (14 == dd.fun(24));
|
|
|
|
|
|
CHECK (15 == dd.fun(25));
|
|
|
|
|
|
|
|
|
|
|
|
// ...use exactly the same configuration,
|
|
|
|
|
|
// but applied in one shot -> chained lazy-Init
|
|
|
|
|
|
dd = LazyDemo{[](int i){return i+1; }}
|
|
|
|
|
|
.attach([](int i){return i/2; });
|
|
|
|
|
|
dd.seed = 3;
|
|
|
|
|
|
CHECK (not dd.isInit());
|
|
|
|
|
|
CHECK (dd.seed == 3);
|
|
|
|
|
|
CHECK (dd.fun);
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (12 == dd.fun(21));
|
|
|
|
|
|
CHECK (13 == dd.fun(22));
|
|
|
|
|
|
CHECK (13 == dd.fun(23));
|
|
|
|
|
|
dd.seed++;
|
|
|
|
|
|
CHECK (14 == dd.fun(23));
|
|
|
|
|
|
CHECK (14 == dd.fun(24));
|
|
|
|
|
|
CHECK (15 == dd.fun(25));
|
|
|
|
|
|
|
|
|
|
|
|
// create a nested graph of chained pending init
|
|
|
|
|
|
dd = LazyDemo{[](int i){return i+1; }};
|
|
|
|
|
|
LazyDemo d1{dd};
|
|
|
|
|
|
LazyDemo d2{move(dd)};
|
|
|
|
|
|
d2.seed = 3;
|
|
|
|
|
|
d2.attach ([](int i){return i/2; });
|
|
|
|
|
|
LazyDemo d3{d2};
|
|
|
|
|
|
d2.attach ([](int i){return i-1; });
|
|
|
|
|
|
|
|
|
|
|
|
// dd was left in defunct state by the move, and thus is locked
|
|
|
|
|
|
CHECK (not dd.fun);
|
|
|
|
|
|
CHECK (dd.isInit());
|
|
|
|
|
|
VERIFY_ERROR (LIFECYCLE, LazyDemo dx{move(dd)} );
|
|
|
|
|
|
// this can be amended by assigning another instance not yet engaged
|
|
|
|
|
|
dd = d2;
|
|
|
|
|
|
d2.seed = 5;
|
|
|
|
|
|
std::swap (d2,d3);
|
|
|
|
|
|
std::swap (d3,d1);
|
|
|
|
|
|
// confused?? ;-)
|
|
|
|
|
|
CHECK (not dd.isInit() and dd.seed == 3); // Seed≡3 {i+1} ⟶ {i/2} ⟶ {i-1}
|
|
|
|
|
|
CHECK (not d1.isInit() and d1.seed == 5); // Seed≡5 {i+1} ⟶ {i/2} ⟶ {i-1}
|
|
|
|
|
|
CHECK (not d2.isInit() and d2.seed == 3); // Seed≡3 {i+1} ⟶ {i/2}
|
|
|
|
|
|
CHECK (not d3.isInit() and d3.seed == 0); // Seed≡0 {i+1}
|
|
|
|
|
|
|
|
|
|
|
|
CHECK (12 == dd.fun(23)); // 23+3 +1 = 27/2 = 13 -1 = 12
|
|
|
|
|
|
CHECK (13 == d1.fun(23)); // 23+5 +1 = 29/2 = 14 -1 = 13
|
|
|
|
|
|
CHECK (13 == d2.fun(23)); // 23+3 +1 = 27/2 = 13 = 13
|
|
|
|
|
|
CHECK (24 == d3.fun(23)); // 23+0 +1 = 24
|
2023-11-25 03:36:19 +01:00
|
|
|
|
}
|
2023-11-23 23:42:55 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Register this test class... */
|
|
|
|
|
|
LAUNCHER (LazyInit_test, "unit common");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}} // namespace lib::test
|