From 67b5df0d1db213a42519f2535250a6a43711ac4f Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Mon, 20 Apr 2015 00:49:49 +0200 Subject: [PATCH] WIP: start factoring out the virtual copy support --- src/lib/meta/virtual-copy-support.hpp | 609 ++++++++++++++++++ src/lib/variant.hpp | 197 +----- .../meta/virtual-copy-support-test.cpp | 286 ++++++++ 3 files changed, 904 insertions(+), 188 deletions(-) create mode 100644 src/lib/meta/virtual-copy-support.hpp create mode 100644 tests/library/meta/virtual-copy-support-test.cpp diff --git a/src/lib/meta/virtual-copy-support.hpp b/src/lib/meta/virtual-copy-support.hpp new file mode 100644 index 000000000..f8a60b618 --- /dev/null +++ b/src/lib/meta/virtual-copy-support.hpp @@ -0,0 +1,609 @@ +/* + VIRTUAL-COPY-SUPPORT.hpp - helper to support copying of erased sub-types + + 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 virtual-copy-support.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. + ** + ** 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 \em all the possible types. As an alternative, you might consider the + ** lib::PolymorphicValue to hold types implementing a common interface. + ** + ** \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). + ** + ** 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. + ** + ** @see Veriant_test + ** @see lib::diff::GenNode + ** + */ + + +#ifndef LIB_META_VIRTUAL_COPY_SUPPORT_H +#define LIB_META_VIRTUAL_COPY_SUPPORT_H + + +#include "lib/meta/typelist.hpp" +#include "lib/meta/typelist-util.hpp" +#include "lib/meta/generator.hpp" + +#include + + +namespace lib { + + using std::string; + + using std::move; + using std::forward; + using util::unConst; + + namespace error = lumiera::error; + + + namespace { // implementation helpers + + using std::is_constructible; + using std::is_move_constructible; + using std::is_copy_constructible; + using std::is_copy_assignable; + using std::remove_reference; + 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"); + }; + + + + + using EmptyBase = struct{}; + + template + class VirtualCopySupportInterface + : public BASE + { + public: + virtual void copyInto (void* targetStorage) const =0; + virtual void moveInto (void* targetStorage) =0; + virtual void copyInto (IFA& target) const =0; + virtual void moveInto (IFA& target) =0; + }; + + + + template + class NoCopyMoveSupport + : public B + { + virtual void + copyInto (void*) const override + { + throw error::Logic("Copy construction invoked but target is noncopyable"); + } + + virtual void + moveInto (void*) override + { + throw error::Logic("Move construction invoked but target is noncopyable"); + } + + virtual void + copyInto (B&) const override + { + throw error::Logic("Assignment invoked but target is not assignable"); + } + + virtual void + moveInto (B&) override + { + throw error::Logic("Assignment invoked but target is not assignable"); + } + }; + + + template + class MoveSupport + : public NoCopyMoveSupport + { + virtual void + copyInto (void*) 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)); + } + }; + + + template + class CloneSupport + : public MoveSupport + { + virtual void + copyInto (void* targetStorage) const override + { + D const& src = static_cast (*this); + new(targetStorage) D(src); + } + }; + + + template + class FullCopySupport + : public CloneSupport + { + 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); + } + }; + + + + + /** workaround for GCC 4.7: need to exclude some types, + * since they raise private access violation during probing. + * Actually, in C++11 such a case should trigger substitution + * failure, not an compilation error */ + template + struct can_use_assignment + : is_copy_assignable + { }; + + template<> + struct can_use_assignment + { static constexpr bool value = false; }; + + + + template + struct use_if_supports_only_move + : enable_if< is_move_constructible::value + && !is_copy_constructible::value + && !can_use_assignment::value + > + { }; + + template + struct use_if_supports_cloning + : enable_if< is_move_constructible::value + && is_copy_constructible::value + && !can_use_assignment::value + > + { }; + + template + struct use_if_supports_copy_and_assignment + : enable_if< is_move_constructible::value + && is_copy_constructible::value + && can_use_assignment::value + > + { }; + + + + + + template + struct CopySupport + { + template + using Policy = NoCopyMoveSupport; + }; + + template + struct CopySupport::type> + { + template + using Policy = MoveSupport; + }; + + template + struct CopySupport::type> + { + template + using Policy = CloneSupport; + }; + + template + struct CopySupport::type> + { + template + using Policy = FullCopySupport; + }; + + + + template + struct ValueAcceptInterface + { + virtual void handle(VAL&) { /* NOP */ }; + }; + + 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 + */ + template + class Variant + { + public: + enum { SIZ = meta::maxSize::value }; + + class Visitor + : public VisitorInterface + { + public: + virtual ~Visitor() { } ///< this is an interface + }; + + + private: + /** Inner capsule managing the contained object (interface) */ + struct Buffer + : VirtualCopySupportInterface + { + char content_[SIZ]; + + void* ptr() { return &content_; } + + + virtual ~Buffer() {} ///< this is an ABC with VTable + + virtual void dispatch (Visitor&) =0; + virtual operator string() const =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 + 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) + { + this->access() = move(rob); + } + + + + 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; + } + + void + dispatch (Visitor& visitor) + { + ValueAcceptInterface& typeDispatcher = visitor; + 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 *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()); + } + + template + Variant(X&& x) + { + using StorageType = typename 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 = typename remove_reference::type; + static_assert (meta::isInList(), + "Type error: the given variant could never hold the required type"); + static_assert (can_use_assignment::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; + } + + + /** 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(); + } + + + void + accept (Visitor& visitor) + { + buffer().dispatch (visitor); + } + }; + + + +} // namespace lib + + + + /* == diagnostic helper == */ + +#ifdef LIB_FORMAT_UTIL_H +namespace lib { + + template + Variant::operator string() const + { + return string(buffer()); + } + + template + template + Variant::Buff::operator string() const + { + return "Variant|" + + util::str (this->access(), + (util::tyStr()+"|").c_str() + ); + } +}// namespace lib + +#endif +#endif /*LIB_META_VIRTUAL_COPY_SUPPORT_H*/ diff --git a/src/lib/variant.hpp b/src/lib/variant.hpp index cffb94804..3dca31677 100644 --- a/src/lib/variant.hpp +++ b/src/lib/variant.hpp @@ -26,7 +26,7 @@ ** 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. @@ -126,192 +126,13 @@ namespace lib { - - - using EmptyBase = struct{}; - - template - class VirtualCopySupportInterface - : public BASE - { - public: - virtual void copyInto (void* targetStorage) const =0; - virtual void moveInto (void* targetStorage) =0; - virtual void copyInto (IFA& target) const =0; - virtual void moveInto (IFA& target) =0; - }; - - - - template - class NoCopyMoveSupport - : public B - { - virtual void - copyInto (void*) const override - { - throw error::Logic("Copy construction invoked but target is noncopyable"); - } - - virtual void - moveInto (void*) override - { - throw error::Logic("Move construction invoked but target is noncopyable"); - } - - virtual void - copyInto (B&) const override - { - throw error::Logic("Assignment invoked but target is not assignable"); - } - - virtual void - moveInto (B&) override - { - throw error::Logic("Assignment invoked but target is not assignable"); - } - }; - - - template - class MoveSupport - : public NoCopyMoveSupport - { - virtual void - copyInto (void*) 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)); - } - }; - - - template - class CloneSupport - : public MoveSupport - { - virtual void - copyInto (void* targetStorage) const override - { - D const& src = static_cast (*this); - new(targetStorage) D(src); - } - }; - - - template - class FullCopySupport - : public CloneSupport - { - 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); - } - }; - - - - - /** workaround for GCC 4.7: need to exclude some types, - * since they raise private access violation during probing. - * Actually, in C++11 such a case should trigger substitution - * failure, not an compilation error */ - template - struct can_use_assignment - : is_copy_assignable - { }; - - template<> - struct can_use_assignment - { static constexpr bool value = false; }; - - - - template - struct use_if_supports_only_move - : enable_if< is_move_constructible::value - && !is_copy_constructible::value - && !can_use_assignment::value - > - { }; - - template - struct use_if_supports_cloning - : enable_if< is_move_constructible::value - && is_copy_constructible::value - && !can_use_assignment::value - > - { }; - - template - struct use_if_supports_copy_and_assignment - : enable_if< is_move_constructible::value - && is_copy_constructible::value - && can_use_assignment::value - > - { }; - - - - - - template - struct CopySupport - { - template - using Policy = NoCopyMoveSupport; - }; - - template - struct CopySupport::type> - { - template - using Policy = MoveSupport; - }; - - template - struct CopySupport::type> - { - template - using Policy = CloneSupport; - }; - - template - struct CopySupport::type> - { - template - using Policy = FullCopySupport; - }; - - - - template - struct ValueAcceptInterface - { - virtual void handle(VAL&) { /* NOP */ }; - }; - - template - using VisitorInterface - = meta::InstantiateForEach; - + /////TODO: *nur noch* + /////TODO: - CopySuport in eigenen Header + /////TODO: - diesen mit Unit-Test covern + /////TODO: - den gefährlichen Cast aus AccessCasted weg + /////TODO: - *nur* der Unit-Test für OpaqueBuffer ist betroffen. Diesen durch direkten statischen Cast ersetzen + /////TODO: - unit-Test für AccessCasted nachliefern + /////TODO: - forward-call für Konstruktor im Buffer? aber nur, wenn es sich verifizieren läßt! }//(End) implementation helpers @@ -364,7 +185,7 @@ namespace lib { /** concrete inner capsule specialised for a given type */ template struct Buff - : CopySupport::template Policy> + : meta::CopySupport::template Policy> { static_assert (SIZ >= sizeof(TY), "Variant record: insufficient embedded Buffer size"); diff --git a/tests/library/meta/virtual-copy-support-test.cpp b/tests/library/meta/virtual-copy-support-test.cpp new file mode 100644 index 000000000..7ad1cea77 --- /dev/null +++ b/tests/library/meta/virtual-copy-support-test.cpp @@ -0,0 +1,286 @@ +/* + VirtualCopySupport(Test) - copy and clone type-erased objects + + Copyright (C) Lumiera.org + 2012, 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. + +* *****************************************************/ + + + +#include "lib/test/run.hpp" +#include "lib/format-string.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/meta/virtual-copy-support.hpp" +#include "lib/util.hpp" + +#include +#include +#include + +using util::_Fmt; +using util::isnil; +using std::string; +using std::cout; +using std::endl; + + +namespace lib { +namespace meta { +namespace test { + + namespace { // Test fixture... + + class Interface; + + /** helper: shortened type display */ + string + typeID(Interface const& obj) + { + string typeStr = lib::test::demangleCxx( + lib::test::showType(obj)); + size_t pos = typeStr.rfind("::"); + if (pos != string::npos) + typeStr = typeStr.substr(pos+2); + return typeStr; + } + + int _CheckSum_ = 0; + + class Interface + { + public: + virtual ~Interface() { } + virtual operator string() const =0; + virtual bool empty() const =0; + }; + + template + class Sub + : public Interface + { + static_assert (0 < i, "invalid construction"); + + char storage_[i]; + + char& + access() + { + return storage_[i-1]; + } + char const& + access() const + { + return storage_[i-1]; + } + + public: + virtual operator string() const override + { + return _Fmt("Sub|%s|%d|-%s") + % typeID(*this) + % i + % access(); + } + + virtual bool + empty() const + { + return !bool(access()); + } + + ~Sub() + { + _CheckSum_ -= access(); + } + Sub() + { + access() = 'A' + rand() % 23; + _CheckSum_ += access(); + } + Sub (Sub const& osub) + { + access() = osub.access(); + _CheckSum_ += access(); + } + Sub (Sub&& rsub) + { + access() = 0; + std::swap(access(),rsub.access()); + } + Sub& + operator= (Sub const& osub) + { + if (!util::isSameObject (*this, osub)) + { + _CheckSum_ -= access(); + access() = osub.access(); + _CheckSum_ += access(); + } + return *this; + } + Sub& + operator= (Sub&& rsub) + { + access() = 0; + std::swap(access(),rsub.access()); + return *this; + } + }; + + + /* == create various flavours of copyable / noncopyable objects == */ + + template + class Regular + : public Sub + { }; + + template + class UnAssignable + : public Sub + { + void operator= (UnAssignable const&); + public: + UnAssignable() = default; + UnAssignable(UnAssignable&&) = default; + UnAssignable(UnAssignable const&) = default; + }; + + template + class OnlyMovable + : public UnAssignable + { + public: + OnlyMovable (OnlyMovable const&) = delete; + OnlyMovable() = default; + OnlyMovable(OnlyMovable&&) = default; + }; + + template + class Noncopyable + : public OnlyMovable + { + public: + Noncopyable (Noncopyable&&) = delete; + Noncopyable () = default; + }; + + }//(End)Test fixture + + + + + + + /**********************************************************************************************//** + * @test verify a mechanism to allow for cloning and placement new of opaque, type-erased entities. + * Basically we allow only assignments and copy between objects of the same concrete type, + * but we want to initiate those operations from the base interface, without any further + * knowledge about the actual types involved. + */ + class VirtualCopySupport_test : public Test + { + virtual void + run (Arg) + { + CHECK(0 == _CheckSum_); + + verify_TestFixture(); + + CHECK(0 == _CheckSum_); + } + + void + verify_TestFixture() + { + /* == full copy, move and assignment == */ + Regular<'A'> a; + Regular<'A'> aa(a); + Regular<'A'> a1; + + cout << string(a) < b; + Interface& ii = b; + + string prevID(b); + ii = a; // Copy operation of Base-Interface is NOP + CHECK (string(b) == prevID); + + + /* == assignment inhibited == */ + UnAssignable<'C'> c; + UnAssignable<'C'> cc(c); + + CHECK (string(c) == string(cc)); + + prevID = cc; + UnAssignable<'C'> ccc(std::move(cc)); + + cout << string(cc) < d; + OnlyMovable<'D'> dd (std::move(d)); + + cout << string(d) < d1 (dd); // does not compile + // d = dd; // does not compile; + + + /* == all copy/assignment inhibited == */ + Noncopyable<'E'> e; + // Noncopyable<'E'> ee (e); // does not compile + // Noncopyable<'E'> eee (std::move (e)); // does not compile + // e = Noncopyable<'E'>(); // does not compile + + CHECK (!isnil (e)); + } + }; + + + /** Register this test class... */ + LAUNCHER (VirtualCopySupport_test, "unit common"); + + + +}}} // namespace lib::meta::test