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:
parent
22f50b1b00
commit
bf9fcc3b2e
2 changed files with 69 additions and 40 deletions
|
|
@ -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&>
|
||||
|
|
|
|||
|
|
@ -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) */
|
||||
|
|
|
|||
Loading…
Reference in a new issue