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_
This commit is contained in:
Fischlurch 2018-04-13 04:19:50 +02:00
parent 22f50b1b00
commit bf9fcc3b2e
2 changed files with 69 additions and 40 deletions

View file

@ -112,7 +112,7 @@ namespace model {
using RawResult = lib::Variant<Types<model::Tangible*, Gtk::Widget*>>;
/** @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<class TAR>
@ -120,43 +120,34 @@ namespace model {
};
namespace {
using TypeSeq = Types<model::Tangible*, Gtk::Widget*>;
using lib::meta::Node;
using lib::meta::NullType;
template<typename T>
struct Identity
{
using Type = T;
};
template<class TYPES, template<class> class _P_>
struct FirstMatching
{
static_assert(not sizeof(TYPES), "None of the possible Types fulfils the condition");
};
template<class...TYPES, template<class> class _P_>
struct FirstMatching<Types<TYPES...>, _P_>
: FirstMatching<typename Types<TYPES...>::List, _P_>
{ };
template<class T, class TYPES, template<class> class _P_>
struct FirstMatching<Node<T,TYPES>, _P_>
: std::conditional_t<_P_<T>::value, Identity<T>, FirstMatching<TYPES, _P_>>
{ };
}
/** @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<class TAR>
struct ElementAccess::TypeConverter
: RawResult::Visitor
{
Result<TAR&> result{"not convertible"};
template<typename X>
using canCast = std::is_convertible<TAR*, X>;
using Base = typename FirstMatching<TypeSeq, canCast>::Type;
template<typename X> // note the "backward" use. We pick that base interface
using canUpcast = std::is_convertible<TAR*, X>; // 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<canUpcast>::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<class TAR>
inline ElementAccess::Result<TAR&>

View file

@ -28,7 +28,7 @@
** but also doesn't deal with alignment issues and is <b>not threadsafe</b>.
**
** 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<typename T>
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 TYPES, template<class> class _P_>
struct FirstMatchingType
{
static_assert(not sizeof(TYPES), "None of the possible Types fulfils the condition");
};
template<class...TYPES, template<class> class _P_>
struct FirstMatchingType<Types<TYPES...>, _P_>
: FirstMatchingType<typename Types<TYPES...>::List, _P_>
{ };
template<class T, class TYPES, template<class> class _P_>
struct FirstMatchingType<Node<T,TYPES>, _P_>
: std::conditional_t<_P_<T>::value, Identity<T>, FirstMatchingType<TYPES, _P_>>
{ };
template<typename RET>
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<typename TYPES>
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<P>::Type`
*/
template<template<class> class _P_>
using FirstMatching = variant::FirstMatchingType<TYPES, _P_>;
private:
/** Inner capsule managing the contained object (interface) */