explicit friendship seems adequate here
DependInject<SRV> becomes more or less a hidden part of Depend<SRV>,
but I prefer to bundle all those quite technical details in a separate
header, and close to the usage
This is a tricky problem an an immediate consequence of the dynamic configuration
favoured by this design. We avoid a centralised configuration and thus there
are no automatic rules to enforce consistency. It would thus be possible
to start using a dependency in singleton style, but to switch to service
style later, after the fact.
An attempt was made to prevent such a mismatch by static initialisiation;
basically the presence of any Depend<SRV>::ServiceInstance<X> would disable
any usage of Depend<SRV> in singleton style. However, such a mechanism
was found to be fragile at best. It seems more apropriate just to fail
when establishing a ServiceInstance on a dependency already actively in
use (and to lock usage after destroying the ServiceInstance).
This issue is considered rather an architectural one, which can not be
solved by any mechanism at implementation level ever
up to now we used placement into a static buffer.
While this approach is somewhat cool, I can't see much practical benefit anymore,
given that we use an elaborate framework which rules out the use of Meyers Singleton.
And given that with C++11 we're able just to use std::unique_ptr to do all work.
Moreover, the intended configurability will become much simpler by relying
on a _closure_ to produce a heap-allocated instance for all cases likewise.
The only possible problem I can see is that critical infrastructure might
rely on failsafe creation of some singleton. Up to now this scenario
remains theoretical however
Meyers Singleton is elegant and fast and considered the default solution
However...
- we want an "instance" pointer that can be rebound and reset,
and thus we are forced to use an explicit Mutex and an atomic variable.
And the situation is such that the optimiser can not detect/verify this usage
and thus generates a spurious additional lock for Meyers Singleton
- we want the option to destroy our singletons explicitly
- we need to create an abstracted closure for the ctor invocation
- we need a compiletime-branch to exclude code generation for invoking
the ctor of an abstract baseclass or interface
All those points would be somehow manageable, but would counterfeit the
simplicity of Meyers Singleton
Problems:
- using Meyers Singleton plus a ClassLock;
This is wasteful, since the compiler will emit additional synchronisation
and will likely not be able to detect the presence of our explicit locking guard
- what happens if the Meyers Singleton can not even be instantiated, e.g. for
an abstract baseclass? We are required to install an explicit subclass configuration
in that case, but the compiler is not able to see this will happen, when just
compiling the lib::Depend
Most dependencies within Lumiera are singletons and this approach remains adequate.
Singletons are not "EVIL" per se. But in some cases, there is an explicit
lifecycle, managed by some subsystem. E.g. some GUI services are only available
while the GTK event loop is running.
This special case can be integrated transparently into our lib::Depend<TY> front-end,
which defaults to creating a singleton otherwise.
Oh well.
This kept me busy a whole day long -- and someone less stubborn like myself
would probably supect a "compiler bug" or put the blame on the language C++
So to stress this point: the compiler behaved CORRECT
Just SFINAE is dangerous stuff: the metafunction I concieved yesterday requires
a complete type, yet, under rather specific circumstances, when instantiating
mutually dependent templates (in our case lib::diff::Record<GenNode> is a
recursive type), the distinction between "complete" and "incomplete"
becomes blurry, and depends on the processing order. Which gave the
misleading impression as if there was a side-effect where the presence
of one definition changes the meaning of another one used in the same
program. What happened in fact was just that the evaluation order was
changed, causing the metafunction to fail silently, thus picking
another specialisation.
This is a consequence of the experiments with generic lambdas.
Up to now, lib::meta::_Fun<F> failed with a compilation error
when passing the decltype of such a generic lambda.
The new behaviour is to pick the empty specialisation (std::false_type) in such cases,
allowing to guard explicit specialisations when no suitable functor type
is passed
...since all those metaprogramming techniques rely on SFINAE,
but *instantiating* a template means to compile it, which is more
than just substituate a type into the signature
If forming the signature fails -> SFINAE, try next one
If instantiating a template fails -> compile error, abort
The key trick is to form an expression with the free function, using a declval of the type to probe.
What is somewhat tricky is the fact that functions can be void, so we need just to pick up
the type and use it in another type expression
...still somewhat unsatisfactory, because
- no clear compile error message when invoking pickArg with insufficient arguments
- the default initialisation case in SelectVararg is duplicated and messy
since the adoption of C++11, we gradually transition our metaprogramming helpers
to support and rely on variadic template parameters. For the time being,
we just augment existing facilities when it comes in handy, yet some more
heavyweight lifting and overall clean-up remains to be done eventually.
seems to be impossible to get rid of the intermediary argument repackaging delegate call.
As always the reason is that argument packs are no real first class types
there is the danger to get into building a fully generic solution, which is
- quite hard / challenging
- counterfeits the goal of writing easy-to-read code
This is the very reason why I do not want to use boost::MPL,
because in our usage situation, their abstractions are not worth
the price in terms of hard to read code.
...since there is not any test coverage for this trait, which
turned out to be quite deeply rooted in the system by now and
handles several rather subtle special cases
the usual suspects...
turns out we need specialisations for those too, even while in most cases
those special reference type won't make it far, and just degrade to function pointer
...to not rely on the old-style signature templates anymore,
i.e. get rid of typename FunctionSignature<function<RET(ARGS...)>>
now, most cases just delegate to the "plain signature" case
..otherwise unchanged.
NOTE: we need two variants, since lambdas are always const functions,
while a member pointer to (non)const function would not be captured
by that overload and thus recurse into the main case and fail there
with "has no operator()"
...and move the tail-call of the template instantiation into try.cpp
This experiment clearly shows the discrepancy now:
- binding a member pointer directly into a function object will expand the argument list
- but binding a similar lambda into a function object won't
(it is not necessary due to the context capture)
The result is that we need to drop support for one of those cases,
and it is clear that the member poiter will be the looser...
It is not clear what would be the 'right' way to handle a member pointer to function
within the function-trait _Fun. The existing implementation choose to inject
an additional parameter for the enclosing class ("this"), which seems to collide
with the intention to use this overload with the "decltype trick" to integrate
support for lambdas.
As it turns out, this specific code path of the existing _Fun trait was not
yet used, fortunately, so we're free to search for the proper design here...
...used in function-closure.hpp to fabricate an argument tuple
for std::bind, for partially closing some function.
This is an attempt to rewrite this somewhat convoluted helper
in a way to fit in with the tuple element picking mechanism
just defined here. Not sure if it's better readable now;
at least it is significantly shorter and omits some
partial specicalisations
because this element picking mechanism for tuples
looks like an instance of something generic.
At least I've written almost the same just some days ago
for the revised version of function-closure, where the
task was to replace a stretch of type arguments in
a given tuple type with a stretch of placeholder types
and then to build a modified ctor, which just fills
in the remaining arguments, while default constructing
the placeholder types. And if we look into the GNU
implementation of std::bind, they're using a similar
concept (with the difference that they're building
a functor object, where we use a type converter)
This refactoring also integrates some generally useful
bits into our standard metaprogramming helper collection
as it turns out, this is a Bug in GCC 4.9 (resolved in 5.x)
See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63723
Problem is, GCC emits a warning on narrowing conversions,
while the standard actually disallows them when building
objects from brace-enclosed initialisers.
Unfortunately GCC also emits such a warning from within
a SFINAE context, instead of letting those spurious dangerous
cases fail. So we end up with additional visitor double dispatch
paths, and a lot of additional warnings.
Temporary solution is to hack a custom trait, which
explicitly declares some conversions paths as "narrowing".
Probably this can be implemented in a way more intelligent
way (using std::numeric_limits), but this doesn't seem
worth the effort, since the problem will go away through
compiler evolution eventually.
now we're able to construct suitable parameter values from the
arguments passed embedded in the GenNodes, as is demonstrated with the
EntryID<long> constructed from an ID-string. We really need a full-blown
double-dispatch, since the content type of the concrete GenNode is only
known at runtime (encoded in the RTTI)
There is still the problem with generating some spurions additional
conversion pathes, some of which are narrowing (and thus dangerous).
The copiler emits several warnings here, and all of them are justified.
E.g. it would be possible to pass an int64_t in the GenNode and initialise
a short from it. This might be convenient at times, but I tend rather to
be prohibitive here and thus consider to built in distinct limitations
on the allowed conversions.
working with those variadic index sequences is quite nasty!
Seemingly you'd always need a 2-step type rebinding,
tried several hours now to avoid that, sorry.
What still needs to be settled is the actual problem
of type conversions; we do not want to be confined
just to the small selection of types allowed within
GenNode, rather we want automatic type promotion
when it comes to extracting values into ctor args!
yeah! it works.
some problems though.
- problem-A : reference arguments
* we're storing the parameters as-is
* for not-yet-bound commands we need to store default constructed values
* together this means we can not handle reference arguments
- problem-B : noncopyable arguments
* especially our Time values are noncopyable.
* this is going to become a huge problem for sure!
this pinpoints a compatibility problem:
our lib::meta::Types<...> type sequence is not compatible
with variadic template parameters, since it uses a fixed
list of NullType default arguments to absorb the variable
number of types.
--> TODO: switch lib::meta::Types to variadic arguments!