/* VERB-VISITOR.hpp - double dispatch to arbitrary functions on a common interface Copyright (C) Lumiera.org 2019, Hermann Vosseler This program 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. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** @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 _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 ** a language comprised of several abstract actions ("verbs"), but to allow the concrete operation ** to be supplied later, at application time, and within a different code context. The most notable ** 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 ** */ #ifndef LIB_VERB_VISITOR_H #define LIB_VERB_VISITOR_H #include "lib/meta/variadic-helper.hpp" #include "lib/polymorphic-value.hpp" #include "lib/verb-token.hpp" #include "lib/symbol.hpp" #include "lib/util.hpp" #include #include #include namespace lib { namespace { using JustSomeIrrelvantType = struct{}; const size_t VERB_TOKEN_SIZE = sizeof(VerbToken); constexpr size_t storageOverhead(size_t argStorage) { return argStorage + VERB_TOKEN_SIZE; } } /** Building block: the Interface to cause the invocation */ template struct VerbInvoker : polyvalue::CloneValueSupport // mark and mix-in virtual copy construction support { virtual ~VerbInvoker() { } virtual RET applyTo (REC&) =0; virtual Literal getID() const =0; bool operator== (VerbInvoker const& o) const { return getID() == o.getID(); } bool operator!= (VerbInvoker const& o) const { return getID() != o.getID(); } }; template struct VerbHolder; /** * Building block: actual storage for a "verb" (function pointer), * together with the pre-bound invocation arguments for this specific operation. */ template struct VerbHolder : VerbInvoker , VerbToken { using Verb = VerbToken; using Args = std::tuple; /** meta-sequence to pick argument values from the storage tuple */ using SequenceIterator = typename meta::BuildIdxIter::Ascending; /** Storage for the argument tuple */ Args args_; template VerbHolder (typename Verb::Handler handlerRef, Literal verbID, PARS&&... args) : Verb{handlerRef, verbID} , args_{std::forward (args)...} { } Literal getID() const override { return Verb::getID(); } RET applyTo (REC& receiver) override { return invokeVerb (receiver, SequenceIterator()); } private: /** @internal actual function invocation, thereby unpacking the argument tuple */ template RET invokeVerb (REC& receiver, meta::IndexSeq) { return (receiver.*Verb::handler_) (std::get (args_)...); } }; /******************************************************************************************//** * A self-contained token to embody a specific yet abstracted operation, * together with a concrete set of suitable arguments. The concrete operation * is suppled on invocation, when the VerbPack is combined with an actual _receiver_ * object, implementing the interface `REC` and thus providing the function implementation. * VerbPack represents a kind of double-dispatch, flexible both on the actual operation * (embodied into the given VerbPack object) and also flexible in the concrete receiver. * @tparam REC the "visitor interface" to invoke operations on * @tparam RET expected (common) return value of the bound operations (can be `void`) * @tparam arg_storage maximum storage size to reserve as buffer for actual function parameters. * @remarks * - binding an operation with arguments exceeding `arg_storage` triggers a static assertion * - the resulting VerbPack object has value semantics and is copyable, to the extent any * embedded function arguments are copyable by themselves. */ template class VerbPack : public PolymorphicValue, storageOverhead(arg_storage)> { using Dispatcher = VerbInvoker; // the interface to talk to our payload using PolyHolder = PolymorphicValue; template struct HandlerTypeDetector { static_assert (!sizeof(FUN), "handler must be a function member pointer for the given receiver"); }; template struct HandlerTypeDetector { using Verb = VerbToken; using Payload = VerbHolder; }; template using PayloadType = typename HandlerTypeDetector::Payload *; public: /** setup a VerbPack for a given operation on the interface `REC` * @param handler function member-pointer to define the operation * @param verbID unique ID to designate the token; equality is based * on this `verbID`, all tokens with same ID count as equal * @param args arbitrary (yet suitable) arguments to pre-bind and use later * when actually invoking the operation on a concrete receiver */ template VerbPack (FUN handler, Literal verbID, ARGS&&... args) : PolyHolder(PayloadType(), handler, verbID, std::forward(args)...) { } /** * Core operation: invoke the operation for this "verb" with the pre-bound parameters * @param receiver a subclass of `REC`, providing the function to invoke * @return result of performing the actual operation */ RET applyTo (REC& receiver) { return PolyHolder::getPayload().applyTo (receiver); } operator string() const { return "VerbPack(" + string{util::unConst(this)->getPayload().getID()} + ")"; } /** unsafe downcast and access to an embedded payload argument value */ template ARG& accessArg () { using EmbeddedPayload = lib::VerbHolder; REQUIRE (INSTANCEOF (EmbeddedPayload, &this->getPayload())); EmbeddedPayload& embedded = static_cast(this->getPayload()); return std::get<0> (embedded.args_); } }; } // namespace lib #endif /*LIB_VERB_VISITOR_H*/