Library: finish and clean-up the solution for VerbPack dispatch
This commit is contained in:
parent
8f43c2591e
commit
3d5a67ed14
4 changed files with 2394 additions and 2376 deletions
|
|
@ -43,7 +43,7 @@
|
|||
** of the programming language, we may try to pull some (template based) trickery
|
||||
** to make polymorphic objects fit better with the handling of small copyable
|
||||
** value objects. Especially, C++ gives a special meaning to passing parameters
|
||||
** as \c const& -- typically constructing an anonymous temporary object conveniently
|
||||
** as `const&` -- typically constructing an anonymous temporary object conveniently
|
||||
** just for passing an abstraction barrier (while the optimiser can be expected to
|
||||
** remove this barrier and the accompanying nominal copy operations altogether in
|
||||
** the generated code). Consequently the ability to return a polymorphic object
|
||||
|
|
@ -68,7 +68,7 @@
|
|||
** Moreover, the PolymorphicValue container provides static builder functions,
|
||||
** allowing to place a concrete instance of a subclass into the content buffer.
|
||||
** After construction, the actual type of this instance will be forgotten
|
||||
** (``type erasure''), but because of the embedded vtable, on access, the
|
||||
** ("type erasure"), but because of the embedded vtable, on access, the
|
||||
** proper implementation functions will be invoked.
|
||||
**
|
||||
** Expanding on that pattern, the copying and cloning operations of the whole
|
||||
|
|
@ -289,19 +289,20 @@ namespace lib {
|
|||
|
||||
|
||||
/**
|
||||
* traits template to deal with
|
||||
* different ways to support copy operations.
|
||||
* trait template to deal with different ways to support copy operations.
|
||||
* Default is no support by the API and implementation types.
|
||||
* In this case, the CopySupport interface is mixed in at the
|
||||
* level of the concrete implementation class and later on
|
||||
* accessed through a \c dynamic_cast
|
||||
* In this case, the CopySupport interface is mixed-in at the
|
||||
* level of the concrete implementation class and will be
|
||||
* accessed later on through a `dynamic_cast<CopyAPI&>`
|
||||
* @todo this whole decision logic works but is confusingly written ///////////////////////TICKET #1197 : improve design of copy support
|
||||
*/
|
||||
template <class TY, class YES = void>
|
||||
struct Trait
|
||||
{
|
||||
typedef CopySupport<TY,EmptyBase> CopyAPI; ///////////////////////////TICKET #1197 : this is naive, we do not know if the target really has full copy support...
|
||||
enum{ ADMIN_OVERHEAD = 2 * sizeof(void*) };
|
||||
using CopyAPI = CopySupport<TY,EmptyBase>; ///////////////////////////TICKET #1197 : this is naive, we do not know if the target really has full copy support...
|
||||
using Assignment = AssignmentPolicy<CopyAPI>;
|
||||
using AdapterAttachment = CopyAPI;
|
||||
enum{ ADMIN_OVERHEAD = 2 * sizeof(void*) }; // need second VTable for CopyAPI mix-in
|
||||
|
||||
static CopyAPI&
|
||||
accessCopyHandlingInterface (TY& bufferContents)
|
||||
|
|
@ -309,9 +310,6 @@ namespace lib {
|
|||
REQUIRE (INSTANCEOF (CopyAPI, &bufferContents));
|
||||
return dynamic_cast<CopyAPI&> (bufferContents);
|
||||
}
|
||||
|
||||
typedef CopyAPI AdapterAttachment;
|
||||
typedef AssignmentPolicy<CopyAPI> Assignment;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -325,8 +323,10 @@ namespace lib {
|
|||
template <class TY>
|
||||
struct Trait<TY, enable_if< exposes_CloneFunction<TY> >>
|
||||
{
|
||||
typedef TY CopyAPI;
|
||||
enum{ ADMIN_OVERHEAD = 1 * sizeof(void*) };
|
||||
using CopyAPI = TY;
|
||||
using Assignment = AssignmentPolicy<CopyAPI>;
|
||||
using AdapterAttachment = struct{ /* irrelevant */ };
|
||||
enum{ ADMIN_OVERHEAD = 1 * sizeof(void*) }; // just the VTable of the payload
|
||||
|
||||
template<class IFA>
|
||||
static CopyAPI&
|
||||
|
|
@ -335,9 +335,6 @@ namespace lib {
|
|||
REQUIRE (INSTANCEOF (CopyAPI, &bufferContents));
|
||||
return static_cast<CopyAPI&> (bufferContents);
|
||||
}
|
||||
|
||||
typedef EmptyBase AdapterAttachment;
|
||||
typedef AssignmentPolicy<CopyAPI> Assignment;
|
||||
};
|
||||
|
||||
}//(End)implementation details
|
||||
|
|
@ -373,7 +370,7 @@ namespace lib {
|
|||
>
|
||||
class PolymorphicValue
|
||||
{
|
||||
public:
|
||||
private:
|
||||
typedef polyvalue::Trait<CPY> _Traits;
|
||||
typedef typename _Traits::CopyAPI _CopyHandlingAdapter;
|
||||
typedef typename _Traits::Assignment _AssignmentPolicy; /////////////////TICKET #1197 : confusingly indirect decision logic
|
||||
|
|
|
|||
|
|
@ -24,12 +24,13 @@
|
|||
/** @file verb-visitor.hpp
|
||||
** A specific double dispatch variation for function invocation.
|
||||
** While the classic visitor invokes a common `handle` function with varying arguments,
|
||||
** here we allow for pre-binding of arbitrary functions on an interface with individual
|
||||
** suitable arguments. Yet similar to the classic visitor, the actual receiver can be a
|
||||
** subclass of the target interface, which causes the _second_ indirection in the dispatch
|
||||
** chain. Since the actually distinguishing factor is not so much a type, but a specific
|
||||
** operation, we refer to the delayed invocation handles created by this binding as
|
||||
** _verb token_ on a _receiver_ object (which is the concrete visitor).
|
||||
** here we allow for pre-binding of arbitrary _handler functions_ on an interface with
|
||||
** together with individual, suitable arguments. Yet similar to the classic visitor, the
|
||||
** _actual receiver_ can be a subclass of the visitor target interface, which causes the
|
||||
** _second_ indirection in the dispatch chain, thus completing a full double-dispatch.
|
||||
** Since the actually distinguishing factor is not so much a type, but a specific operation,
|
||||
** we refer to the delayed invocation handles created by this binding as _verb token_
|
||||
** on a _receiver_ object (which is the concrete visitor).
|
||||
**
|
||||
** This setup is an extension or derivative of the [generic verb token](\ref verb-token-hpp)
|
||||
** used for the diff system and similar applications; likewise the intended usage is to establish
|
||||
|
|
@ -38,6 +39,43 @@
|
|||
** use case is for the drawing of track contents in the user interface, where this pattern allows
|
||||
** the separation of actual drawing code from the nested track controller structure.
|
||||
**
|
||||
**
|
||||
** ## implementation technique
|
||||
**
|
||||
** The actual foundation is quite simple: we store a [member pointer]. Typically, this embedded
|
||||
** pointer-to-member shall be bound to an abstract virtual function on the _visitor interface._
|
||||
** So basically the "verb" boils down to storing an offset into the VTable on the interface.
|
||||
** Later, on invocation, a reference to the actual _receiver_ is passed in, typically a concrete
|
||||
** subclass of the visitor interface. The invocation then combines this receiver reference with
|
||||
** the offset (the member pointer) to invoke the desired virtual function.
|
||||
**
|
||||
** However, the complications and technicalities arise from the ability to bind arbitrary
|
||||
** function signatures, even together with the actual arguments to use at invocation. Those
|
||||
** function arguments are supplied when creating the "packaged verb", and thus need to be stored
|
||||
** within this package, together with the member-pointer. The result is a _materialised_ and
|
||||
** _delayed_ invocation of an abstract (interface) function, while the actual concrete function
|
||||
** implementation shall be supplied later. Obviously, such a ["verb pack"](\ref VerbPack) has
|
||||
** _value semantics_ -- we want to store it, copy it and pass it along, often even within a
|
||||
** sequence of "verbs". And even more: we do not want to pass "hidden references" and we
|
||||
** do not want to rely on some management service and heap allocations. Rather, each
|
||||
** VerbPack shall be a self-contained value object. Within certain limitations,
|
||||
** this is possible in C++ by using an opaque buffer embedded within the
|
||||
** outer value object; basically the pre-defined buffer size must be
|
||||
** sufficient to hold all possible argument tuples to bind.
|
||||
**
|
||||
** The actual implementation here relies on two other components from the Lumiera library:
|
||||
** - the lib::VerbToken provides us with the dispatch through a stored member pointer
|
||||
** - the lib::PolymorphicValue allows to embed a subclass within an opaque inline buffer,
|
||||
** just exposing the common interface.
|
||||
** Yet another challenge is the necessity to unpack the argument values from the storage
|
||||
** tuple and pass them to an (at this point abstracted) function with arbitrary signature.
|
||||
** Here we rely on the common implementation technique from [std::apply], here with the
|
||||
** special twist that we don't use a pre-bound function, but rather need to combine the
|
||||
** actual invocation target at the moment of the invocation.
|
||||
**
|
||||
** [member pointer]: https://en.cppreference.com/w/cpp/language/pointer
|
||||
** [std::apply]: https://en.cppreference.com/w/cpp/utility/apply
|
||||
**
|
||||
** @see [drawing on the track canvas](\ref body-canvas-widget.cpp)
|
||||
** @see VerbVisitorDispatch_test
|
||||
**
|
||||
|
|
@ -72,11 +110,10 @@ namespace lib {
|
|||
}
|
||||
}
|
||||
|
||||
using EmptyBasE = struct { };
|
||||
|
||||
template<class REC, class RET>
|
||||
struct VerbInvoker
|
||||
: polyvalue::CloneValueSupport<EmptyBasE> // mark and mix-in virtual copy construction support
|
||||
: polyvalue::CloneValueSupport<polyvalue::EmptyBase> // mark and mix-in virtual copy construction support
|
||||
{
|
||||
virtual ~VerbInvoker() { }
|
||||
|
||||
|
|
@ -84,10 +121,10 @@ namespace lib {
|
|||
};
|
||||
|
||||
template<class REC, class SIG>
|
||||
struct Holder;
|
||||
struct VerbHolder;
|
||||
|
||||
template<class REC, class RET, typename... ARGS>
|
||||
struct Holder<REC, RET(ARGS...)>
|
||||
struct VerbHolder<REC, RET(ARGS...)>
|
||||
: VerbInvoker<REC,RET>
|
||||
, VerbToken<REC,RET(ARGS...)>
|
||||
{
|
||||
|
|
@ -99,7 +136,7 @@ namespace lib {
|
|||
|
||||
Args args_;
|
||||
|
||||
Holder (typename Verb::Handler handlerRef, Literal verbID, ARGS&&... args)
|
||||
VerbHolder (typename Verb::Handler handlerRef, Literal verbID, ARGS&&... args)
|
||||
: Verb{handlerRef, verbID}
|
||||
, args_{std::forward<ARGS> (args)...}
|
||||
{ }
|
||||
|
|
@ -128,7 +165,7 @@ namespace lib {
|
|||
using PolyHolder = PolymorphicValue<VerbInvoker<REC,RET>, storageOverhead(arg_storage)>;
|
||||
|
||||
template<typename...ARGS>
|
||||
using PayloadType = Holder<REC, RET(ARGS...)>*;
|
||||
using PayloadType = VerbHolder<REC, RET(ARGS...)>*;
|
||||
|
||||
template<typename...ARGS>
|
||||
using Handler = typename VerbToken<REC, RET(ARGS...)>::Handler;
|
||||
|
|
|
|||
|
|
@ -155,22 +155,6 @@ namespace test{
|
|||
TokenSeq tokens = build_and_copy_tokens();
|
||||
render_verbose (tokens);
|
||||
// profile.append_woof(1, 2);
|
||||
|
||||
using IrrelvantType = struct{};
|
||||
const size_t VERB_TOK_SIZE = sizeof(lib::VerbToken<IrrelvantType, void(void)>);
|
||||
using HoldL = Holder<Receiver, string(string)>;
|
||||
const size_t VERB_TOK_SIZ2 = sizeof(HoldL::Verb);
|
||||
const size_t HOLDER_SIZE = sizeof(HoldL);
|
||||
const size_t ARG_TUP_SIZE = sizeof(std::tuple<string>);
|
||||
|
||||
SHOW_EXPR (VERB_TOK_SIZE);
|
||||
SHOW_EXPR (VERB_TOK_SIZ2);
|
||||
SHOW_EXPR (HOLDER_SIZE);
|
||||
SHOW_EXPR (ARG_TUP_SIZE);
|
||||
SHOW_EXPR (sizeof(string));
|
||||
using AdapT = Token::Adapter<HoldL>;
|
||||
SHOW_TYPE (AdapT);
|
||||
SHOW_EXPR (sizeof(AdapT));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue