diff --git a/src/lib/meta/virtual-copy-support.hpp b/src/lib/meta/virtual-copy-support.hpp
index f8a60b618..bdfd070b8 100644
--- a/src/lib/meta/virtual-copy-support.hpp
+++ b/src/lib/meta/virtual-copy-support.hpp
@@ -22,45 +22,88 @@
/** @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.
+ ** Helper for building "virtual copy" operations.
+ ** Especially in conjunction with type erasure, sometimes we have to deal with
+ ** situations, where we want to copy or assign an object without knowing the
+ ** full implementation type. Obviously, the default (compiler generated) operations
+ ** will not work in such situations -- even worse, they will slice the objects on
+ ** copy. The reason is: we \em must know the full implementation and storage layout
+ ** of a type do provide any meaningful copy, assignment or move operation.
**
- ** 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.
+ ** A possible workaround is to call into the actual implementation type through
+ ** a virtual function (which requires a VTable): Even if for everyone else any
+ ** knowledge regarding the exact implementation type has been discarded ("erased"),
+ ** the function pointers in the VTable still implicitly hold onto that precise
+ ** implementation type, since they were setup during construction, where the
+ ** type was still available. Such a scheme of dealing with "opaque" copy operations
+ ** is known as virtual copy -- it can be dangerous and tricky to get right
+ ** and is preferably used only in flat class hierarchies.
**
- ** \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).
+ ** This helper template simplifies the construction of such a scheme.
+ ** - a base interface defines the available virtual copy operations
+ ** - a set of CRTP-style templates covers all the case of
+ ** - full copy support
+ ** - copy construction but not assignment
+ ** - only move construction allowed
+ ** - noncopyable type
+ ** - we use type traits and a policy template to pick the correct implementation
+ ** for a given data type. Any assignment or copy operations not supported by the
+ ** target type will be replaced by an implementation which raises a runtime error
+ ** (Exception).
**
- ** 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.
+ ** \par prerequisites
+ ** The actual implementation type needs to provide the necessary standard / custom
+ ** copy and assignment operations with the usual signature. Moreover, the implementation
+ ** type provides a static function \c downcast(Base&) to perform a suitable dynamic
+ ** or static downcast from the interface type to the concrete implementation type.
**
- ** @see Veriant_test
- ** @see lib::diff::GenNode
+ ** \par usage
+ ** The provided virtual operations are to be used in "backward" direction: invoked
+ ** on the source and affecting the target. The source, through the VTable, knows its
+ ** precise type and data layout. The target is handed in as parameter and treated
+ ** by the \c downcast() function -- which preferably performs a dynamic cast
+ ** (or at least asserts the correct type). This whole scheme can only work for
+ ** copy and assignment of objects, which actually have the same implementation
+ ** type -- it will never be possible to "cross copy" to an completely unrelated
+ ** target type, at least not generically.
+ **
+ ** Usage example
+ ** \code{.cpp}
+ ** Variant (Variant const& ref)
+ ** {
+ ** ref.buffer().copyInto (&storage_);
+ ** }
+ **
+ ** Variant&
+ ** operator= (Variant const& ovar)
+ ** {
+ ** ovar.buffer().copyInto (this->buffer());
+ ** return *this;
+ ** }
+ **
+ ** Buffer const&
+ ** buffer() const
+ ** {
+ ** return *reinterpret_cast (&storage_);
+ ** }
+ ** \endcode
+ ** In this example, the concrete implementation of the \c Buffer interface
+ ** will mix in the Policy template with the implementations of the virtual copy
+ ** operations. The copy constructor uses the virtual \c copyInto(void*) to perform
+ ** a "placement new", whereas the assignment operator calls the virtual \c copyInto(Buffer&)
+ ** to downcast the target \c Buffer and in the end invokes the assignment operator on
+ ** the concrete \c Buffer implementation subclass.
+ **
+ ** @warning please make sure that \em only the virtual copy operation is invoked, since
+ ** this operation will delegate to the copy operation on the implementation class
+ ** and thus already invoke the whole chain of custom / compiler generated copy
+ ** implementations. Ignoring this warning can lead to insidious slicing or partial
+ ** copies. Additionally, if you \em really need multiple level deep inheritance,
+ ** you need to mix in the copy implementations on \em every level \em again, and
+ ** you need to provide custom copy operations on every level.
+ **
+ ** @see VirtualCopySupport_test
+ ** @see lib::Variant usage example
**
*/
@@ -69,541 +112,207 @@
#define LIB_META_VIRTUAL_COPY_SUPPORT_H
-#include "lib/meta/typelist.hpp"
-#include "lib/meta/typelist-util.hpp"
-#include "lib/meta/generator.hpp"
+#include "lib/error.hpp"
#include
-
+#include
namespace lib {
-
- using std::string;
+namespace time{
+ class Time; // forward declaration for GCC 4.7 workaround
+}}
+
+namespace lib {
+namespace meta{
using std::move;
- using std::forward;
using util::unConst;
+ using std::is_move_constructible;
+ using std::is_copy_constructible;
+ using std::is_copy_assignable;
+ using std::enable_if;
+
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
+ using EmptyBase = struct{};
-
- /**
- * 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
+ template
+ class VirtualCopySupportInterface
+ : public BASE
{
public:
- enum { SIZ = meta::maxSize::value };
-
- class Visitor
- : public VisitorInterface
+ 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
{
- 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_);
+ throw error::Logic("Copy construction invoked but target is noncopyable");
}
- template
- Buff&
- buff()
+ virtual void
+ moveInto (void*) override
{
- return Buff::downcast(this->buffer());
+ throw error::Logic("Move construction invoked but target is noncopyable");
}
-
- public:
- ~Variant()
+ virtual void
+ copyInto (I&) const override
{
- buffer().~Buffer();
+ throw error::Logic("Assignment invoked but target is not assignable");
}
- Variant()
+ virtual void
+ moveInto (I&) override
{
- using DefaultType = typename TYPES::List::Head;
-
- new(storage_) Buff (DefaultType());
+ 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");
}
- template
- Variant(X&& x)
+ virtual void
+ moveInto (void* targetStorage) override
{
- using StorageType = typename CanBuildFrom::Type;
-
- new(storage_) Buff (forward(x));
+ 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 (I& target) const override
+ {
+ D& t = D::downcast(target);
+ D const& s = static_cast (*this);
+ t = s;
}
- Variant (Variant& ref)
+ virtual void
+ moveInto (I& target) override
{
- 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);
+ D& t = D::downcast(target);
+ D& s = static_cast (*this);
+ t = move(s);
}
};
-} // namespace lib
-
-
-
- /* == diagnostic helper == */
-
-#ifdef LIB_FORMAT_UTIL_H
-namespace lib {
- template
- Variant::operator string() const
- {
- return string(buffer());
- }
+ /** 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
- template
- Variant::Buff::operator string() const
- {
- return "Variant|"
- + util::str (this->access(),
- (util::tyStr()+"|").c_str()
- );
- }
-}// namespace lib
-
-#endif
+ 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
+ >
+ { };
+
+
+
+
+ /** Policy to pick a suitable implementation of "virtual copy operations".
+ * @note You need to mix in the VirtualCopySupportInterface
+ */
+ 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;
+ };
+
+
+
+
+}} // namespace lib::meta
#endif /*LIB_META_VIRTUAL_COPY_SUPPORT_H*/
diff --git a/src/lib/variant.hpp b/src/lib/variant.hpp
index 3dca31677..13a80091e 100644
--- a/src/lib/variant.hpp
+++ b/src/lib/variant.hpp
@@ -61,6 +61,7 @@
**
** @see Veriant_test
** @see lib::diff::GenNode
+ ** @see virtual-copy-support.hpp
**
*/
@@ -72,8 +73,11 @@
#include "lib/meta/typelist.hpp"
#include "lib/meta/typelist-util.hpp"
#include "lib/meta/generator.hpp"
+#include "lib/meta/virtual-copy-support.hpp"
#include
+#include
+#include
namespace lib {
@@ -89,12 +93,7 @@ namespace lib {
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;
@@ -126,9 +125,18 @@ namespace lib {
+ 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
@@ -168,7 +176,7 @@ namespace lib {
private:
/** Inner capsule managing the contained object (interface) */
struct Buffer
- : VirtualCopySupportInterface
+ : meta::VirtualCopySupportInterface
{
char content_[SIZ];
@@ -347,7 +355,7 @@ namespace lib {
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");
+ static_assert (meta::can_use_assignment::value, "target type does not support assignment");
buff() = forward(x);
return *this;
diff --git a/tests/12metaprogramming.tests b/tests/12metaprogramming.tests
index 2580cdeda..39bce4b62 100644
--- a/tests/12metaprogramming.tests
+++ b/tests/12metaprogramming.tests
@@ -771,3 +771,7 @@ END
+TEST "Virtual copying of opaque objects" VirtualCopySupport_test <
+ 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
@@ -38,6 +38,9 @@ using std::string;
using std::cout;
using std::endl;
+using lumiera::error::LUMIERA_ERROR_LOGIC;
+using lumiera::error::LUMIERA_ERROR_WRONG_TYPE;
+
namespace lib {
namespace meta {
@@ -61,7 +64,30 @@ namespace test {
int _CheckSum_ = 0;
+
+ /** Interface for the Virtual copy operations.
+ * @remarks we define this explicitly here for the tests solely.
+ * In real use, you'd just mix in VirtualCopySupportInterface.
+ * But since we want to verify the text fixture in isolation,
+ * we use here empty base implementations instead of pure
+ * virtual functions, so we can always instantiate all
+ * test classes.
+ */
+ class CopyInterface
+ {
+ public:
+ virtual void copyInto (void* ) const { /* NOP */ };
+ virtual void moveInto (void* ) { /* NOP */ };
+ virtual void copyInto (Interface&) const { /* NOP */ };
+ virtual void moveInto (Interface&) { /* NOP */ };
+ };
+
+
+ /**
+ * The official Interface for our test class hierarchy
+ */
class Interface
+ : public CopyInterface
{
public:
virtual ~Interface() { }
@@ -69,6 +95,9 @@ namespace test {
virtual bool empty() const =0;
};
+ /**
+ * implementation class with "special" memory layout
+ */
template
class Sub
: public Interface
@@ -88,7 +117,8 @@ namespace test {
return storage_[i-1];
}
- public:
+ public: /* == Implementation of the Interface == */
+
virtual operator string() const override
{
return _Fmt("Sub|%s|%d|-%s")
@@ -103,6 +133,9 @@ namespace test {
return !bool(access());
}
+
+ public: /* == full set of copy and assignment operations == */
+
~Sub()
{
_CheckSum_ -= access();
@@ -136,6 +169,7 @@ namespace test {
Sub&
operator= (Sub&& rsub)
{
+ _CheckSum_ -= access();
access() = 0;
std::swap(access(),rsub.access());
return *this;
@@ -154,7 +188,7 @@ namespace test {
class UnAssignable
: public Sub
{
- void operator= (UnAssignable const&);
+ void operator= (UnAssignable const&); // private and unimplemented
public:
UnAssignable() = default;
UnAssignable(UnAssignable&&) = default;
@@ -180,9 +214,50 @@ namespace test {
Noncopyable () = default;
};
+
+
+ /* == concrete implementation subclass with virtual copy support == */
+
+ template
+ class Opaque //-----Interface| CRTP-Impl | direct Baseclass
+ : public CopySupport::template Policy, IMP>
+ {
+ public:
+ static Opaque&
+ downcast (Interface& bas)
+ {
+ Opaque* impl = dynamic_cast (&bas);
+
+ if (!impl)
+ throw error::Logic("virtual copy works only on instances "
+ "of the same concrete implementation class"
+ ,error::LUMIERA_ERROR_WRONG_TYPE);
+ else
+ return *impl;
+ }
+ };
+
+ // == Test subject(s)==========================
+ using RegularImpl = Opaque>;
+ using ClonableImpl = Opaque>;
+ using MovableImpl = Opaque>;
+ using ImobileImpl = Opaque>;
+
}//(End)Test fixture
+// GCC 4.7 workaround
+// SFINAE does not work properly on private functions
+// instead of dropping the template instance, it causes compilation failure
+
+}//now in namespace meta
+
+ template
+ struct can_use_assignment>
+ { static constexpr bool value = false; };
+
+namespace test {
+
@@ -203,8 +278,28 @@ namespace test {
verify_TestFixture();
CHECK(0 == _CheckSum_);
+
+ verify_fullVirtualCopySupport();
+ verify_noAssignementSupport();
+ verify_onlyMovableSupport();
+ verify_disabledCopySupport();
+
+ CHECK(0 == _CheckSum_);
}
+
+ /** @test our test fixture is comprised of
+ * - a common interface (#Interface)
+ * - a implementation template #Sub to hold a buffer and
+ * manage a distinct random value at some position in that buffer,
+ * which depends on the concrete implementation type
+ * - layered on top are adapters to make this implementation class
+ * either fully copyable, non-assignable, only movable or noncopyable.
+ * - a global checksum, based on the random value of all instances,
+ * which are incremented on construction and decremented on destruction.
+ * After destroying everything this checksum should go to zero.
+ * This test case just verifies this implementation mechanic.
+ */
void
verify_TestFixture()
{
@@ -221,6 +316,12 @@ namespace test {
CHECK (string(a) == string(aa));
CHECK (string(a) == string(a1));
+ CHECK (!isnil(a1));
+
+ a = std::move(a1);
+
+ CHECK (isnil(a1));
+ CHECK (string(a) == string(aa));
/* == interface vs. concrete class == */
@@ -275,6 +376,168 @@ namespace test {
CHECK (!isnil (e));
}
+
+
+
+
+
+
+ void
+ verify_fullVirtualCopySupport()
+ {
+ RegularImpl a,aa,aaa;
+ Interface& i(a);
+ Interface& ii(aa);
+ Interface& iii(aaa);
+
+ char storage[sizeof(RegularImpl)];
+ Interface& iiii (*reinterpret_cast (&storage));
+
+ string prevID = a;
+ CHECK (!isnil (a));
+
+ i.moveInto(&storage);
+ CHECK (string(iiii) == prevID);
+ CHECK (!isnil(iiii));
+ CHECK ( isnil(i));
+
+ ii.copyInto(i);
+ CHECK (!isnil(i));
+ CHECK (!isnil(ii));
+ CHECK (string(i) == string(ii));
+
+ prevID = iii;
+ iii.moveInto(ii);
+ CHECK (!isnil(ii));
+ CHECK ( isnil(iii));
+ CHECK (string(ii) == prevID);
+
+ // Verify that type mismatch in assignment is detected...
+ Opaque> divergent;
+ Interface& evil(divergent);
+ VERIFY_ERROR (WRONG_TYPE, evil.copyInto(i));
+ VERIFY_ERROR (WRONG_TYPE, evil.moveInto(i));
+
+
+ cout << "==fullVirtualCopySupport=="< (&storage));
+
+ string prevID = b;
+ CHECK (!isnil (b));
+
+ i.moveInto(&storage);
+ CHECK (string(iiii) == prevID);
+ CHECK (!isnil(iiii));
+ CHECK ( isnil(i));
+
+ iiii.~Interface(); //clean-up previously placed instance
+
+ prevID = ii;
+ ii.copyInto(&storage);
+ CHECK (!isnil(ii));
+ CHECK (!isnil(iiii));
+ CHECK ( isnil(i));
+ CHECK (string(iiii) == prevID);
+ CHECK (string(ii) == prevID);
+
+ prevID = iii;
+ VERIFY_ERROR (LOGIC, iii.copyInto(ii));
+ VERIFY_ERROR (LOGIC, iii.moveInto(ii));
+ CHECK (string(iii) == prevID);
+ CHECK (!isnil(iii));
+
+ cout << "==noAssignementSupport=="< (&storage));
+
+ string prevID = i;
+ CHECK (!isnil (i));
+
+ i.moveInto(&storage);
+ CHECK (string(iiii) == prevID);
+ CHECK (!isnil(iiii));
+ CHECK ( isnil(i));
+
+ prevID = ii;
+ VERIFY_ERROR (LOGIC, ii.copyInto(&storage));
+ VERIFY_ERROR (LOGIC, ii.copyInto(i));
+ VERIFY_ERROR (LOGIC, ii.moveInto(i));
+ CHECK (string(ii) == prevID);
+ CHECK (!isnil(ii));
+ CHECK ( isnil(i));
+
+ cout << "==onlyMovableSupport=="<