From bf9fcc3b2e9840fccc1bb115175c2a874c8839c6 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 13 Apr 2018 04:19:50 +0200 Subject: [PATCH] ElementAccess: make the metaprogramming helper part of lib::Variant ...since such a metafunction makes sense, generally. Get me the first of the possible variant types, which fulfils predicate _P_ --- src/gui/model/element-access.hpp | 58 ++++++++++++++------------------ src/lib/variant.hpp | 51 ++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 40 deletions(-) diff --git a/src/gui/model/element-access.hpp b/src/gui/model/element-access.hpp index 7f9dfd6eb..684fdd55c 100644 --- a/src/gui/model/element-access.hpp +++ b/src/gui/model/element-access.hpp @@ -112,7 +112,7 @@ namespace model { using RawResult = lib::Variant>; /** @internal drill down according to coordinates, maybe create element */ - virtual RawResult performAccessTo (UICoord, size_t limitCreation) =0; + virtual RawResult performAccessTo (UICoord, size_t limitCreation) =0; private: template @@ -120,43 +120,34 @@ namespace model { }; - namespace { - using TypeSeq = Types; - - using lib::meta::Node; - using lib::meta::NullType; - - template - struct Identity - { - using Type = T; - }; - - template class _P_> - struct FirstMatching - { - static_assert(not sizeof(TYPES), "None of the possible Types fulfils the condition"); - }; - template class _P_> - struct FirstMatching, _P_> - : FirstMatching::List, _P_> - { }; - template class _P_> - struct FirstMatching, _P_> - : std::conditional_t<_P_::value, Identity, FirstMatching> - { }; - - } + + + /** @internal helper to perform conversion to the desired result type. + * This is a bit of tricky metaprogramming to deal with the problem that we can not + * assume a single base interface for all the UI elements or widgets accessible through + * UI-Coordinates. Rather we have to deal with a small set of possible base interfaces, + * and thus the actual [access function](\ref #performAccessTo) returns a _variant record_ + * holding a pointer, and internally tagged with the base interface type to apply. Now the + * public API functions are templated to the _result type as expected by the invoking clinent_ + * and thus we get a matrix of possible cases; when the expected result type is _reachable by + * dynamic downcast_ from the actual base interface type returned by the internal access function, + * we can perform this dynamic_cast. Otherwise the returned result proxy object is marked as empty. + * @remark technically this solution relies on the lib::Variant::Visitor to provide a NOP default + * implementation. The TypeConverter template is instantiated with the desired target type + * and thus only overwrites _the first case where an conversion is possible._ In all other + * cases the default implementation does nothing and thus retains the embedded result proxy + * in empty state. + */ template struct ElementAccess::TypeConverter : RawResult::Visitor { Result result{"not convertible"}; - template - using canCast = std::is_convertible; - - using Base = typename FirstMatching::Type; + template // note the "backward" use. We pick that base interface + using canUpcast = std::is_convertible; // into which our desired result type can be upcast, because + // we know then the following dynamic_cast (downcast) can succeed + using Base = typename RawResult::FirstMatching::Type; void accept (Base* pb) @@ -193,7 +184,8 @@ namespace model { * @return suitably converted direct (language) reference to the desired element * wrapped as _result proxy_ * @note when access was not possible because the element could not been created, - * the result proxy is empty and convertible to `bool(false)` + * or is not convertible to the desired target type, the result proxy is empty, + * and convertible to `bool(false)` (or raises an exception on attempted access) */ template inline ElementAccess::Result diff --git a/src/lib/variant.hpp b/src/lib/variant.hpp index 238320f14..b74fae86c 100644 --- a/src/lib/variant.hpp +++ b/src/lib/variant.hpp @@ -28,7 +28,7 @@ ** but also doesn't deal with alignment issues and is not threadsafe. ** ** Deliberately, the design rules out re-binding of the contained type. Thus, - ** once created, a variant \em must hold a valid element and always an element + ** once created, a variant _must hold a valid element_ and always an element ** of the same type. Beyond that, variant elements are copyable and mutable. ** Direct access requires knowledge of the embedded type (no switch-on type). ** Type mismatch is checked at runtime. As a fallback, we provide a visitor @@ -39,10 +39,11 @@ ** Likewise, we do not want to support mutations of the variant type at runtime. Basically, ** using a variant record is recommended only if either the receiving context has structural ** knowledge about the type to expect, or when a visitor implementation can supply a sensible - ** handling for \em all the possible types. As an alternative, you might consider the + ** handling for _all the possible types._ As an alternative, you might consider the ** lib::PolymorphicValue to hold types implementing a common interface. ** - ** \par implementation notes + ** ## implementation notes + ** ** We use a "double capsule" implementation technique similar to lib::OpaqueHolder. ** In fact, Variant is almost identical to the latter, just omitting unnecessary flexibility. ** The outer capsule exposes the public handling interface, while the inner, private capsule @@ -61,9 +62,9 @@ ** ** @note we use a Visitor interface generated through metaprogramming. ** This may generate a lot of warnings "-Woverloaded-virtual", - ** since one \c handle(TX) function may shadow other \c handle(..) functions + ** since one `handle(TX)` function may shadow other `handle(..)` functions ** from the inherited (generated) Visitor interface. These warnings are besides - ** the point, since not the \em client uses these functions, but the Variant does, + ** the point, since not the _client_ uses these functions, but the Variant does, ** after upcasting to the interface. Make sure you define your specialisations with ** the override modifier; when done so, it is safe to disable this warning here. ** @@ -102,10 +103,11 @@ namespace lib { namespace error = lumiera::error; - namespace variant { // implementation helpers + namespace variant { // implementation metaprogramming helpers using std::remove_reference; using meta::NullType; + using meta::Types; using meta::Node; @@ -148,6 +150,33 @@ namespace lib { { }; + template + struct Identity { using Type = T; }; + + /** + * Helper to pick the first type from a type sequence, + * which fulfils the predicate (meta function) given as template + * @tparam TYPES a type sequence or type list + * @tparam a predicate template or type trait + * @note result as embedded typedef `Type` + */ + template class _P_> + struct FirstMatchingType + { + static_assert(not sizeof(TYPES), "None of the possible Types fulfils the condition"); + }; + + template class _P_> + struct FirstMatchingType, _P_> + : FirstMatchingType::List, _P_> + { }; + + template class _P_> + struct FirstMatchingType, _P_> + : std::conditional_t<_P_::value, Identity, FirstMatchingType> + { }; + + template struct VFunc @@ -188,7 +217,7 @@ namespace lib { * @todo we need to define all copy operations explicitly, due to the * templated one-arg ctor to wrap the actual values. * There might be a generic solution for that ////////////////////////TICKET #963 Forwarding shadows copy operations -- generic solution?? - * But -- Beware of unverifiable generic solutions! + * But -- Beware of unverifiable generic solutions! */ template class Variant @@ -219,6 +248,14 @@ namespace lib { virtual ~Predicate() { } ///< this is an interface }; + /** + * Metafunction to pick the first of the variant's types, + * which satisfies the given trait or predicate template + * @note result is the embedded typedef `FirstMatching

::Type` + */ + template class _P_> + using FirstMatching = variant::FirstMatchingType; + private: /** Inner capsule managing the contained object (interface) */