/* VARIANT.hpp - lightweight typesafe union record Copyright (C) Lumiera.org 2015, 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 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 \em 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. ** ** \par implementation notes ** We use a similar "double capsule" implementation technique as for 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). ** ** @see Veriant_test ** @see lib::diff::GenNode ** */ #ifndef LIB_VARIANT_H #define LIB_VARIANT_H #include "lib/meta/typelist.hpp" #include "lib/meta/typelist-util.hpp" //#include "lib/util.hpp" #include #include //#include //#include //#include namespace lib { using std::string; using std::move; using std::forward; using util::unConst; namespace error = lumiera::error; template class Variant; namespace { // implementation helpers using std::is_constructible; using std::remove_reference; using std::remove_const; using std::is_same; using std::enable_if; using meta::NullType; using meta::Node; template struct CanBuildFrom : CanBuildFrom::type ,typename TYPES::List > { }; template struct CanBuildFrom> { using Type = X; }; template struct CanBuildFrom> { using Type = typename CanBuildFrom::Type; }; template struct CanBuildFrom { static_assert (0 > sizeof(X), "No type in Typelist can be built from the given argument"); }; template struct Bare { using Type = typename remove_const< typename remove_reference::type>::type; }; template struct is_Variant { static constexpr bool value = false; }; template struct is_Variant> { static constexpr bool value = true; }; template struct use_if_is_Variant : enable_if::Type>::value, V> { }; template class FullCopySupport : public B { virtual void copyInto (void* targetStorage) const override { D const& src = static_cast (*this); new(targetStorage) D(src); } virtual void moveInto (void* targetStorage) override { D& src = static_cast (*this); new(targetStorage) D(move(src)); } virtual void copyInto (B& target) const override { D& t = D::downcast(target); D const& s = static_cast (*this); t = s; } virtual void moveInto (B& target) override { D& t = D::downcast(target); D& s = static_cast (*this); t = move(s); } }; template class CloneSupport : public B { virtual void copyInto (void* targetStorage) const override { D const& src = static_cast (*this); new(targetStorage) D(src); } virtual void moveInto (void* targetStorage) override { D& src = static_cast (*this); new(targetStorage) D(move(src)); } virtual void copyInto (B& target) const override { throw error::Logic("Assignment invoked but target is not assignable"); } virtual void moveInto (B& target) override { throw error::Logic("Assignment invoked but target is not assignable"); } }; template class MoveSupport : public B { virtual void copyInto (void* targetStorage) const override { throw error::Logic("Copy construction invoked but target allows only move construction"); } virtual void moveInto (void* targetStorage) override { D& src = static_cast (*this); new(targetStorage) D(move(src)); } virtual void copyInto (B& target) const override { throw error::Logic("Assignment invoked but target is not assignable"); } virtual void moveInto (B& target) override { throw error::Logic("Assignment invoked but target is not assignable"); } }; template class NoCopyMoveSupport : public B { virtual void copyInto (void* targetStorage) const override { throw error::Logic("Copy construction invoked but target is noncopyable"); } virtual void moveInto (void* targetStorage) override { throw error::Logic("Move construction invoked but target is noncopyable"); } virtual void copyInto (B& target) const override { throw error::Logic("Assignment invoked but target is not assignable"); } virtual void moveInto (B& target) override { throw error::Logic("Assignment invoked but target is not assignable"); } }; template struct CopySupport { template using Policy = NoCopyMoveSupport; }; }//(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. */ template class Variant { enum { SIZ = meta::maxSize::value }; /** Inner capsule managing the contained object (interface) */ struct Buffer { char content_[SIZ]; void* ptr() { return &content_; } virtual ~Buffer() {} ///< this is an ABC with VTable virtual void copyInto (void* targetStorage) const =0; virtual void moveInto (void* targetStorage) =0; virtual void copyInto (Buffer& target) const =0; virtual void moveInto (Buffer& target) =0; // virtual void operator= (Buffer const&) =0; // virtual void operator= (Buffer&&) =0; }; /** concrete inner capsule specialised for a given type */ template struct Buff : 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 *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 assignValue (TY const& ob) { if (&ob != Buffer::ptr()) this->access() = ob; } void assignValue (TY && rob) { this->access() = move(rob); } /* == virtual access functions == */ virtual void copyInto (void* targetStorage) const override { new(targetStorage) Buff(this->access()); } // virtual void // operator= (Buffer const& ob) override // { // assignValue (downcast(unConst(ob)).access()); // } // // virtual void // operator= (Buffer&& rref) override // { // assignValue (move (downcast(rref).access())); // } static Buff& downcast (Buffer& b) { Buff* buff = dynamic_cast (&b); if (!buff) throw error::Logic("Variant type mismatch: " "the given variant record does not hold " "a value of the type requested here" ,error::LUMIERA_ERROR_WRONG_TYPE); else return *buff; } }; 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]; template struct Assign { using RawType = typename remove_reference::type; static_assert (meta::isInList(), "Type error: the given variant could never hold the required type"); static void assign (Buffer& buffer, X&& x) { buff().assignValue (forward(x)); } }; template struct Assign::type> { static void assign (Buffer& buffer, V&& oVar) { // oVar.buffer() // buff().assignValue (forward(oVar)); } }; protected: /* === internal interface for managing the storage === */ Buffer& buffer() { return *reinterpret_cast (&storage_); } Buffer const& buffer() const { return *reinterpret_cast (&storage_); } template Buff& buff() { return Buff::downcast(this->buffer()); } public: ~Variant() { buffer().~Buffer(); } Variant() { using DefaultType = typename TYPES::List::Head; new(storage_) Buff (DefaultType()); } Variant (Variant& ref) { ref.buffer().copyInto (&storage_); } Variant (Variant const& ref) { ref.buffer().copyInto (&storage_); } Variant (Variant&& rref) { rref.buffer().copyInto (&storage_); } template Variant(X&& x) { using StorageType = typename CanBuildFrom::Type; new(storage_) Buff (forward(x)); } template Variant& operator= (X x) { // using RawType = typename remove_reference::type; // static_assert (meta::isInList(), // "Type error: the given variant could never hold the required type"); // // buff().assignValue (forward(x)); Assign::assign (this->buffer(), forward (x)); return *this; } /* Variant& operator= (Variant& ovar) { buffer() = ovar.buffer(); return *this; } Variant& operator= (Variant const& ovar) { buffer() = ovar.buffer(); return *this; } Variant& operator= (Variant&& rvar) { buffer().operator= (move(rvar.buffer())); return *this; } */ #ifdef LIB_TEST_TEST_HELPER_H /* == diagnostic helper == */ operator string() const { UNIMPLEMENTED("diagnostic string conversion"); } #endif /* === Access === */ template X& get() { static_assert (meta::isInList(), "Type error: the given variant could never hold the required type"); return buff().access(); } class Visitor { public: virtual ~Visitor() { } ///< this is an interface }; void accept (Visitor& visitor) { UNIMPLEMENTED("visitor style value access"); } }; } // namespace lib #endif /*LIB_VARIANT_H*/