/* VARIANT.hpp - lightweight typesafe union record Copyright (C) 2015, Hermann Vosseler   **Lumiera** 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. See the file COPYING for further details. */ /** @file variant.hpp ** A typesafe union record to carry embedded values of unrelated type. ** This file defines a simple alternative to boost::variant. It pulls in ** fewer headers, has a shorter code path and is hopefully more readable, ** 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 _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 ** scheme for generic access. ** ** The design restrictions were chosen deliberately, since a variant type might ** promote "probe and switch on type" style programming, which is known to be fragile. ** 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 _all the possible types._ As an alternative, you might consider the ** lib::PolymorphicValue to hold types implementing a common interface. ** ** # 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 ** is a polymorphic value holder. Since C++ as such does not support polymorphic values, ** the inner capsule is placed "piggyback" into a char buffer. The actual value is carried ** within yet another, nested char buffer. Thus, effectively the first "slot" of the storage ** will hold the VTable pointer, thereby encoding the actual type information -- leading to ** a storage requirement of MAX plus one "slot" for the VTable. (with "slot" we ** denote the smallest disposable storage size for the given platform after alignment, ** typically the size of a size_t). ** ** To support copying and assignment of variant instances, but limit these operations ** to variants holding the same type, we use a virtual assignment function. In case the ** concrete type does not support assignment or copy construction, the respective access ** function is replaced by an implementation raising a runtime error. ** ** @note we use a Visitor interface generated through metaprogramming. ** This may generate a lot of warnings "-Woverloaded-virtual", ** 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 _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. ** ** @see Veriant_test ** @see lib::diff::GenNode ** @see virtual-copy-support.hpp ** */ #ifndef LIB_VARIANT_H #define LIB_VARIANT_H #include "lib/error.hpp" #include "lib/meta/typelist.hpp" #include "lib/meta/typelist-util.hpp" #include "lib/meta/generator.hpp" #include "lib/meta/virtual-copy-support.hpp" #include "lib/format-obj.hpp" #include "lib/util.hpp" #include #include #include #include namespace lib { using std::string; using std::move; using std::forward; using util::unConst; namespace error = lumiera::error; namespace variant { // implementation metaprogramming helpers using std::remove_reference; using meta::Types; using meta::Node; using meta::Nil; template struct CanBuildFrom : CanBuildFrom::type ,typename TYPES::List > { }; template struct CanBuildFrom> : std::true_type { using Type = X; }; template struct CanBuildFrom> : std::true_type { using Type = X; }; template struct CanBuildFrom> ///< esp. allow to build string from char literal : std::true_type { using Type = string; }; template struct CanBuildFrom> : CanBuildFrom { }; template struct CanBuildFrom : std::false_type { }; 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 { /** how to treat one single type in visitation */ template struct ValueAcceptInterface { virtual RET handle(VAL&) { /* do nothing */ return RET(); }; }; /** build a generic visitor interface for all types in list */ template using VisitorInterface = meta::InstantiateForEach; }; }//(End) implementation helpers /** * Typesafe union record. * A Variant element may carry an embedded value of any of the predefined types. * The type may not be rebound: It must be created holding some value and each * instance is fixed to the specific type used at construction time. * Yet within the same type, variant elements are copyable and assignable. * The embedded type is erased on the signature, but knowledge about the * actual type is retained, encoded into the embedded VTable. Thus, * any access to the variant's value requires knowledge of the type * in question, but type mismatch will provoke an exception at runtime. * Generic access is possible using a visitor. * @warning not threadsafe * @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! */ template class Variant { public: enum { SIZ = meta::maxSize() , ALIGN = meta::maxAlign() }; template using VisitorFunc = variant::VFunc::template VisitorInterface; template using VisitorConstFunc = variant::VFunc::template VisitorInterface>; /** * to be implemented by the client for visitation * @see #accept(Visitor&) */ class Visitor : public VisitorFunc { public: virtual ~Visitor() { } ///< this is an interface }; class Predicate : public VisitorConstFunc { public: virtual ~Predicate() { } ///< this is an interface }; class Renderer : public VisitorConstFunc { public: virtual ~Renderer() { } ///< this is an interface }; ///////////////////////////////////TICKET #1361 : unable to make the Visitor fully generic /** * 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) */ struct Buffer : meta::VirtualCopySupportInterface { alignas(ALIGN) std::byte content_[SIZ]; void* ptr() { return &content_; } virtual ~Buffer() {} ///< this is an ABC with VTable virtual void dispatch (Visitor&) =0; virtual bool dispatch (Predicate&) const =0; virtual string dispatch (Renderer&) const =0; virtual operator string() const =0; }; /** concrete inner capsule specialised for a given type */ template struct Buff : meta::CopySupport::template Policy> { static_assert (SIZ >= sizeof(TY), "Variant record: insufficient embedded Buffer size"); TY& access() const ///< core operation: target is contained within the inline buffer { return * std::launder (reinterpret_cast (unConst(this)->ptr())); } ~Buff() { access().~TY(); } Buff (TY const& obj) { new(Buffer::ptr()) TY(obj); } Buff (TY && robj) { new(Buffer::ptr()) TY(move(robj)); } Buff (Buff const& oBuff) { new(Buffer::ptr()) TY(oBuff.access()); } Buff (Buff && rBuff) { new(Buffer::ptr()) TY(move (rBuff.access())); } void operator= (Buff const& buff) { *this = buff.access(); } void operator= (Buff&& rref) { *this = move (rref.access()); } void operator= (TY const& ob) { if (&ob != Buffer::ptr()) this->access() = ob; } void operator= (TY && rob) { if (&rob != Buffer::ptr()) this->access() = move(rob); } static string indicateTypeMismatch (Buffer&); static Buff& downcast (Buffer& b) { Buff* buff = dynamic_cast (&b); if (!buff) throw error::Logic(indicateTypeMismatch(b) ,LERR_(WRONG_TYPE)); else return *buff; } void dispatch (Visitor& visitor) { using Dispatcher = variant::VFunc::template ValueAcceptInterface; Dispatcher& typeDispatcher = visitor; typeDispatcher.handle (this->access()); } bool dispatch (Predicate& visitor) const { using Dispatcher = variant::VFunc::template ValueAcceptInterface; Dispatcher& typeDispatcher = visitor; return typeDispatcher.handle (this->access()); } string dispatch (Renderer& visitor) const { using Dispatcher = variant::VFunc::template ValueAcceptInterface; Dispatcher& typeDispatcher = visitor; return typeDispatcher.handle (this->access()); } /** diagnostic helper */ operator string() const; }; enum{ BUFFSIZE = sizeof(Buffer) }; /** embedded buffer actually holding the concrete Buff object, * which in turn holds and manages the target object. * @note Invariant: always contains a valid Buffer subclass */ char storage_[BUFFSIZE]; protected: /* === internal interface for managing the storage === */ Buffer& buffer() { return * std::launder (reinterpret_cast (&storage_)); } Buffer const& buffer() const { return * std::launder (reinterpret_cast (&storage_)); } template Buff& buff() { return Buff::downcast(this->buffer()); } /** @internal for derived classes to implement custom access logic */ template X* maybeGet() { Buff* buff = dynamic_cast*> (& this->buffer()); if (buff) return & buff->access(); else return nullptr; } public: ~Variant() { buffer().~Buffer(); } Variant() { using DefaultType = TYPES::List::Head; new(storage_) Buff (DefaultType()); } template Variant(X&& x) { static_assert (variant::CanBuildFrom(), "No type in Typelist can be built from the given argument"); using StorageType = variant::CanBuildFrom::Type; new(storage_) Buff (forward(x)); } Variant (Variant& ref) { ref.buffer().copyInto (&storage_); } Variant (Variant const& ref) { ref.buffer().copyInto (&storage_); } Variant (Variant&& rref) { rref.buffer().moveInto (&storage_); } template Variant& operator= (X x) { using RawType = std::remove_reference::type; static_assert (meta::isInList(), "Type error: the given variant could never hold the required type"); static_assert (std::is_copy_assignable::value, "target type does not support assignment"); buff() = forward(x); return *this; } Variant& operator= (Variant& ovar) { ovar.buffer().copyInto (this->buffer()); return *this; } Variant& operator= (Variant const& ovar) { ovar.buffer().copyInto (this->buffer()); return *this; } Variant& operator= (Variant&& rvar) { rvar.buffer().moveInto (this->buffer()); return *this; } //note: NOT defining a swap operation, because swapping inline storage is pointless! /** diagnostic helper */ operator string() const; /* === Access === */ template X& get() { static_assert (meta::isInList(), "Type error: the given variant could never hold the required type"); return buff().access(); } template X const& get() const { return unConst(this)->template get(); } void accept (Visitor& visitor) { buffer().dispatch (visitor); } bool accept (Predicate& visitor) const { return buffer().dispatch (visitor); } string accept (Renderer& visitor) const { return buffer().dispatch (visitor); } }; /* == diagnostic helper == */ template Variant::operator string() const { return "Variant|" + string(buffer()); } template template Variant::Buff::operator string() const { return util::typedString (this->access()); } /** * error message when accessing the variant content with wrong type assumptions. * @remark while this diagnostics can be crucial for finding bugs, we avoid * including \ref format-string.hpp, since lib::Variant is used pervasively * as part of lib::diff::GenNode. Especially in development builds, we observed * a tangible leverage on executable size. Thus we implement the protection * against follow-up exceptions explicitly here. */ template template inline string Variant::Buff::indicateTypeMismatch(Variant::Buffer& target) { try { return "Variant type mismatch: expected value of type «" + lib::meta::typeStr()+"», " + "however the given variant record is " + string{target}; } catch(...) { return lib::meta::FAILURE_INDICATOR; } } }// namespace lib #endif /*LIB_VARIANT_H*/