So we have now a reworked version of the internals of TreeExplorer in place.
It should be easier to debug template instantation traces now, since most
of the redundancy on the type parameters could be remove. Moreover, existing
pipelines can now be re-assigned with similarily built pipelines in many cases,
since the concrete type of the functor is now erased.
The price tag for this refactoring is that we have now to perform a call
through a function pointer on each functor invocation (due to the type erasure).
And seemingly the bloat in the debugging information has been increased slightly
(this overhead is removed by stripping the binary)
Here the design trardeoff becomes clearly visiblie
- on the plus side, we removed that spurous redundant info
from the template parameter, and we simplified functor rebinding
- but as a tradeoff, we now always have two std::function objects
nested into each other, which also means that at least the outer
object resides on the heap and /inevitably/ calls through a
function pointer, even in case the target function is a lambda,
simply because some type erasure happened, and the call site
does not know the actual type anymore
...step by step switch over to the new usage pattern.
Transformer should be the blueprint for all other functor usages.
The reworked solutions behaves as expected;
we see two functor invocations; the outer functor, which does
the argument adaptation, is allocated in heap memory
This does not touch the existing code-path,
but the idea is to use the _FunTraits directly from within the
constructor of the respective processing layer, and to confine the
knowledge of the actual FUN functor type to within that limited context.
Only the generic signature of the resulting std::function need to be
encoded into the type of the processing component, which should help
to simplify the type signatures
...which still needs to be the *concrete* signature of the funcition to pass,
but we'll attempt to loosen that requirement in the next refactoring steps
...and in preparation start with some renamings...
The overall goal is to simplify the type signatures and thereby
to make the generates pipelines more assignment compatible.
The debugging experience form the last days indicated that the
current design is not maintainable on the long run. Both the
template instantiation chains and the call stacks are way to
complicated and hard to understand and diagnose
It is essential that we pass the current state of the filter
into the expand functor, where it needs to be copied (once!)
to create a child state, which can then be augmented.
This augmented state is then pushed onto a stack, to enable backtracking.
Due to the flexible adapters and the wrapping into the TreeExplorer builder,
we ended up performing several spurious copies on the current state
...based on a monadic tree expansion: we define a single step,
which takes the current filter configuration and builds the next
filter configuration, based on a stored chain of configuration functions
The actual exhausting depth-first results just by the greedy application pattern,
and uses the stack embedded in the "Explorer" layer of TreeExplorer
..this resolves the most challenging part of the construction work;
we use the static helper functions to infer a type and construct a suitable
processing pipeline and we invoke the same helper to initialise the base class
in the ctor.
Incidentally... we can now drop all the placeholder stubs,
since we now inherit the full iterator and child explorer API.
The test now starts actually to work... we get spam and sausage!
TODO: now actually fill in the expand functor such as to pick the
concrete filter step in the chain from a sequence of preconfigured
filter bindings
...now matters start to get really nasty,
since we have to pick up an infered type from a partially built pipeline
and use it to construct the signature for a functor to bind into the more elaborate complete pipeline
this is a tricky undertaking, since our treeExplore() helper constructs
a complex wrapped type, depending on the actual builder expressions used.
Solution is to use decltype on the result of a helper function,
and let the _DecoratorTraits from TreeExplorer do the necessary type adaptations
when adapting a functor, the wrapper automatically decides
if this functor was meant to be used in "monadic style" (value -> new monad)
or in "manipulate state-core style" (iter& -> iter)
Unfortunately, in some cases functions accepting a partially built pipeline
will be classified in the wrong way, due to the fact that IterableDecorator
has a templated wildcard constructor and thus seems to accept the conversion
value_type -> IterableDecorator. This causes the "monadic style" to be chosen
erroneously, and leads to a template instantiation failure just at the point when
the generated functor will be used.
The solution is to explicitly *rule out* the monadic usage style
in all those cases, where the *argument* of the functor to bind can
in fact be directly constructed / converted from the source iterator
or state core. Because, if that is the case
- it is superfluous explicitly to dereference the source iterator;
the functor can do the same
- chances are that the "manipulation style" was intended
(as was the case in the concrete example at hand here)
...it should have been this way all the time.
Generic code might otherwise be ill guided to assume a conversion
from the Iterator to its value type, while in fact an explicit dereferentiation is necessary
Need to be more careful when eating trailing spaces after a std::string.
Because the full-blown type is a template, sometimes the compiler adds a spurious
additional space behind the closing angle bracket, due to the now obsolete
"maximum munch rule" of C++98 -- to prevent closing angle brackets to become '>>'.
But in other cases, some type adornments or other language identifiers follow the
string type, like e.g. 'const'. In those cases, the trailing space must be retained.
We solve this by a look-ahead assertion in the regular expression:
consume the trailing space _only_ if a non-word character follows (like '>').
The intention is to augment the iterator based (linear) search
used in EventLog to allow for real backtracking, based on a evaluation tree.
This should be rather staight forward to implement, relying on the
exploreChildren() functionality of TreeExplorer. The trick is to package
the chained search step as a monadic flatMap operation
while this is basically a drop-in replacement,
it marks the switch to the monadic evaluation technology,
which is prerequisite for building real backtracking into the search.
we did an unnecessary copy of the argument, which was uncovered
by the test case manipulating the state core.
Whew.
Now we have a beautiful new overengineered solution
outift the Filter base class with the most generic form of the Functor
wrapper, and rather wrap each functor argument individually. This allows
then to combine various kinds of functors
...this solution works, but has a shortcoming:
the type of the passed lambdas is effectively pinned to conform
with the signature of the first lambda used initially when building the filter.
Well, this is the standard use case, but it kind of turns all the
tricky warpping and re-binding into a nonsense excercise; in this form
the filter can only be used in the monadic case (value -> bool).
Especially this rules out all the advanced usages, where the filter
collaborates with the internals of the source.
while this is basically just code code cosmetics,
at least it marks this as a very distinct special case,
and keeps the API for the standard Filter layer clean.
a quite convoluted construct built from several nested generic lambdas.
When investigated in the debugger, the observed addresses and the
invoked code looks sane and as expected.
The intention is to switch from the itertools-based filter
to the filter available in the TreeExplorer framework.
Thus "basically" we just need to copy the solution over,
since both are conceptually equivalent.
However...... :-(
The TreeExplorer framework is designed to be way more generic
and accepts basically everything as argument and tries to adapt apropriately.
This means we have to use a lot of intricate boilerplate code,
just to get the same effect that was possible in Itertools with
a simple and elegant in-place lambda assignment
Fillter needs to be re-evaluated, when an downstream entity requests
expandChildren() onto an upstream source. And obviously the ordering
of the chained calls was wrong here.
As it turns out, I had discovered that necessity to re-evaluate with
the Transformer layer. There is a dedicated test case for that, but
I cut short on verifying the filter in that situation as well, so
that piece of broken copy-n-paste code went through undetected.
This is in fact a rather esoteric corner case, because it is only
triggered when the expandChildren() call is passed through the filter.
When otoh the filter sits /after/ the entity generating the expandChildren()
calls, everything works as intended. And the latter is the typical standard
usage situation of an recursive evalutation algorithm: the filter is here
used as final part to drive the evaluation ahead and pick the solutions.
There is a bug or shortcoming in the existing ErrorLog matcher implementation.
It is not really difficult to fix, however doing so would require us to intersperse
yet another helper facility into the log matcher. And it occurred to me, that
this helper would effectively re-implement the stack based backtracking ability,
which is already present in TreeExplorer (and was created precisely to support
this kind of recursive evaluation strategies).
Thus I intend to switch the implementation of the EventLog matcher from the
old IterTool framework to the newer TreeExplorer framework. And this intention
made me re-read the code, fixing several comments and re-thinking the design
seemingly my quick-n-dirty implementation was to naiive.
We need real backtracking, if we want to support switches
in the search direction (match("y").after("x").before("z")
Up to now, I have cheated myself around this obvious problem :-/
this is a (hopefully just temporary) workaround to deal with static initialisation
ordering problems. The original solution was cleaner from a code readability viewpoint,
however, when lib::Depend was used from static initialisation code, it could
be observed that the factory constructor was invoked after first use.
And while this did not interfer with the instance lifecycle management itself,
because the zero-initialisation of the instance (atomic) pointer did happen
beforehand, it would discard any special factory functions installed from such
a context (and this counts as bug for my taste).
seemingly this code was brittle: GCC-7 treats int64_t as long,
which leads to preferring the template specialisation over the
explicit version of the operator* -- which means the template
instantiation invokes itself.
usually the ID is hard coded, but when re-throwing errors, it might be
from "somewhere else", which means it is possibly a NULL ptr.
In those cases we fall back to the cannonical ID of the error class.
Basically the mocking mechanism just switches the configuration
and then waits for the service to be accessed in order to cause acutual
instantiation of the mock service implementation. But sometimes we want
to prepare and rig the mock instance prior to the first invocation;
in such cases it can be handy just to trigger the lazy creating process
while switching various services to the new framework,
I noticed the requirement to create a service handle in not-yet-started mode
and then start it explicitly, maybe even from another thread. Thus I introduced
a no-arg default ctor for that purpose, but overlooked that the forwarding ctor
might also need zero arguments for default constructible service implementation
classes. Thus I've now introduced a marker ENUM for disambiguation
SingletonRef was only invented because lib::Depend (or lib::Singleton at that time)
offered only on-demand initialisation, but could not attach to an external service.
But this is required for calling out at the implementation side of a
Lumiera Interface into the actual service implementation.
The recently created DependInject::ServiceInstance now fulfils this task way better
and is seamlessly integrated into the lib::Depend front-end
Actually this is on the implementation side only.
Since Layer-Separation-Interfaces route each call through a binding layer,
we get two Service-"Instances" to manage
- on the client side we have to route into the Lumiera Interface system
- on the implementation side the C-Language calls from the Interface system
need to get to the actual service implementation. The latter is now
managed and exposed via DependInject::ServiceInstance