virtual copy support documented and covered with unit test
This commit is contained in:
parent
67b5df0d1d
commit
de50bf7c91
5 changed files with 532 additions and 547 deletions
|
|
@ -22,45 +22,88 @@
|
||||||
|
|
||||||
|
|
||||||
/** @file virtual-copy-support.hpp
|
/** @file virtual-copy-support.hpp
|
||||||
** A typesafe union record to carry embedded values of unrelated type.
|
** Helper for building "virtual copy" operations.
|
||||||
** This file defines a simple alternative to boost::variant. It pulls in
|
** Especially in conjunction with type erasure, sometimes we have to deal with
|
||||||
** fewer headers, has a shorter code path and is hopefully more readable,
|
** situations, where we want to copy or assign an object without knowing the
|
||||||
** but also doesn't deal with alignment issues and is <b>not threadsafe</b>.
|
** full implementation type. Obviously, the default (compiler generated) operations
|
||||||
**
|
** will not work in such situations -- even worse, they will slice the objects on
|
||||||
** Deliberately, the design rules out re-binding of the contained type. Thus,
|
** copy. The reason is: we \em must know the full implementation and storage layout
|
||||||
** once created, a variant \em must hold a valid element and always an element
|
** of a type do provide any meaningful copy, assignment or move operation.
|
||||||
** 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
|
** A possible workaround is to call into the actual implementation type through
|
||||||
** promote "probe and switch on type" style programming, which is known to be fragile.
|
** a virtual function (which requires a VTable): Even if for everyone else any
|
||||||
** Likewise, we do not want to support mutations of the variant type at runtime. Basically,
|
** knowledge regarding the exact implementation type has been discarded ("erased"),
|
||||||
** using a variant record is recommended only if either the receiving context has structural
|
** the function pointers in the VTable still implicitly hold onto that precise
|
||||||
** knowledge about the type to expect, or when a visitor implementation can supply a sensible
|
** implementation type, since they were setup during construction, where the
|
||||||
** handling for \em all the possible types. As an alternative, you might consider the
|
** type was still available. Such a scheme of dealing with "opaque" copy operations
|
||||||
** lib::PolymorphicValue to hold types implementing a common interface.
|
** is known as <b>virtual copy</b> -- it can be dangerous and tricky to get right
|
||||||
|
** and is preferably used only in flat class hierarchies.
|
||||||
**
|
**
|
||||||
** \par implementation notes
|
** This helper template simplifies the construction of such a scheme.
|
||||||
** We use a similar "double capsule" implementation technique as for lib::OpaqueHolder.
|
** - a base interface defines the available virtual copy operations
|
||||||
** In fact, Variant is almost identical to the latter, just omitting unnecessary flexibility.
|
** - a set of CRTP-style templates covers all the case of
|
||||||
** The outer capsule exposes the public handling interface, while the inner, private capsule
|
** - full copy support
|
||||||
** is a polymorphic value holder. Since C++ as such does not support polymorphic values,
|
** - copy construction but not assignment
|
||||||
** the inner capsule is placed "piggyback" into a char buffer. The actual value is carried
|
** - only move construction allowed
|
||||||
** within yet another, nested char buffer. Thus, effectively the first "slot" of the storage
|
** - noncopyable type
|
||||||
** will hold the VTable pointer, thereby encoding the actual type information -- leading to
|
** - we use type traits and a policy template to pick the correct implementation
|
||||||
** a storage requirement of MAX<TYPES...> plus one "slot" for the VTable. (with "slot" we
|
** for a given data type. Any assignment or copy operations not supported by the
|
||||||
** denote the smallest disposable storage size for the given platform after alignment,
|
** target type will be replaced by an implementation which raises a runtime error
|
||||||
** typically the size of a size_t).
|
** (Exception).
|
||||||
**
|
**
|
||||||
** To support copying and assignment of variant instances, but limit these operations
|
** \par prerequisites
|
||||||
** to variants holding the same type, we use a virtual assignment function. In case the
|
** The actual implementation type needs to provide the necessary standard / custom
|
||||||
** concrete type does not support assignment or copy construction, the respective access
|
** copy and assignment operations with the usual signature. Moreover, the implementation
|
||||||
** function is replaced by an implementation raising a runtime error.
|
** 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
|
** \par usage
|
||||||
** @see lib::diff::GenNode
|
** 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<const Buffer*> (&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
|
#define LIB_META_VIRTUAL_COPY_SUPPORT_H
|
||||||
|
|
||||||
|
|
||||||
#include "lib/meta/typelist.hpp"
|
#include "lib/error.hpp"
|
||||||
#include "lib/meta/typelist-util.hpp"
|
|
||||||
#include "lib/meta/generator.hpp"
|
|
||||||
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace lib {
|
namespace lib {
|
||||||
|
namespace time{
|
||||||
using std::string;
|
class Time; // forward declaration for GCC 4.7 workaround
|
||||||
|
}}
|
||||||
|
|
||||||
|
namespace lib {
|
||||||
|
namespace meta{
|
||||||
|
|
||||||
using std::move;
|
using std::move;
|
||||||
using std::forward;
|
|
||||||
using util::unConst;
|
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 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<typename X, typename TYPES>
|
|
||||||
struct CanBuildFrom
|
|
||||||
: CanBuildFrom<typename remove_reference<X>::type
|
|
||||||
,typename TYPES::List
|
|
||||||
>
|
|
||||||
{ };
|
|
||||||
|
|
||||||
template<typename X, typename TYPES>
|
|
||||||
struct CanBuildFrom<X, Node<X, TYPES>>
|
|
||||||
{
|
|
||||||
using Type = X;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename X, typename T,typename TYPES>
|
|
||||||
struct CanBuildFrom<X, Node<T, TYPES>>
|
|
||||||
{
|
|
||||||
using Type = typename CanBuildFrom<X,TYPES>::Type;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename X>
|
|
||||||
struct CanBuildFrom<X, NullType>
|
|
||||||
{
|
|
||||||
static_assert (0 > sizeof(X), "No type in Typelist can be built from the given argument");
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
using EmptyBase = struct{};
|
|
||||||
|
|
||||||
template<class IFA, class BASE = EmptyBase>
|
|
||||||
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 B, class D>
|
|
||||||
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 B, class D>
|
|
||||||
class MoveSupport
|
|
||||||
: public NoCopyMoveSupport<B, D>
|
|
||||||
{
|
|
||||||
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<D&> (*this);
|
|
||||||
new(targetStorage) D(move(src));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
template<class B, class D>
|
|
||||||
class CloneSupport
|
|
||||||
: public MoveSupport<B,D>
|
|
||||||
{
|
|
||||||
virtual void
|
|
||||||
copyInto (void* targetStorage) const override
|
|
||||||
{
|
|
||||||
D const& src = static_cast<D const&> (*this);
|
|
||||||
new(targetStorage) D(src);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
template<class B, class D>
|
|
||||||
class FullCopySupport
|
|
||||||
: public CloneSupport<B,D>
|
|
||||||
{
|
|
||||||
virtual void
|
|
||||||
copyInto (B& target) const override
|
|
||||||
{
|
|
||||||
D& t = D::downcast(target);
|
|
||||||
D const& s = static_cast<D const&> (*this);
|
|
||||||
t = s;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void
|
|
||||||
moveInto (B& target) override
|
|
||||||
{
|
|
||||||
D& t = D::downcast(target);
|
|
||||||
D& s = static_cast<D&> (*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<class X>
|
|
||||||
struct can_use_assignment
|
|
||||||
: is_copy_assignable<X>
|
|
||||||
{ };
|
|
||||||
|
|
||||||
template<>
|
|
||||||
struct can_use_assignment<lib::time::Time>
|
|
||||||
{ static constexpr bool value = false; };
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
template<class X>
|
|
||||||
struct use_if_supports_only_move
|
|
||||||
: enable_if< is_move_constructible<X>::value
|
|
||||||
&& !is_copy_constructible<X>::value
|
|
||||||
&& !can_use_assignment<X>::value
|
|
||||||
>
|
|
||||||
{ };
|
|
||||||
|
|
||||||
template<class X>
|
|
||||||
struct use_if_supports_cloning
|
|
||||||
: enable_if< is_move_constructible<X>::value
|
|
||||||
&& is_copy_constructible<X>::value
|
|
||||||
&& !can_use_assignment<X>::value
|
|
||||||
>
|
|
||||||
{ };
|
|
||||||
|
|
||||||
template<class X>
|
|
||||||
struct use_if_supports_copy_and_assignment
|
|
||||||
: enable_if< is_move_constructible<X>::value
|
|
||||||
&& is_copy_constructible<X>::value
|
|
||||||
&& can_use_assignment<X>::value
|
|
||||||
>
|
|
||||||
{ };
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
template<class X, class SEL=void>
|
|
||||||
struct CopySupport
|
|
||||||
{
|
|
||||||
template<class B, class D>
|
|
||||||
using Policy = NoCopyMoveSupport<B,D>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class X>
|
|
||||||
struct CopySupport<X, typename use_if_supports_only_move<X>::type>
|
|
||||||
{
|
|
||||||
template<class B, class D>
|
|
||||||
using Policy = MoveSupport<B,D>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class X>
|
|
||||||
struct CopySupport<X, typename use_if_supports_cloning<X>::type>
|
|
||||||
{
|
|
||||||
template<class B, class D>
|
|
||||||
using Policy = CloneSupport<B,D>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class X>
|
|
||||||
struct CopySupport<X, typename use_if_supports_copy_and_assignment<X>::type>
|
|
||||||
{
|
|
||||||
template<class B, class D>
|
|
||||||
using Policy = FullCopySupport<B,D>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
template<class VAL>
|
|
||||||
struct ValueAcceptInterface
|
|
||||||
{
|
|
||||||
virtual void handle(VAL&) { /* NOP */ };
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename TYPES>
|
|
||||||
using VisitorInterface
|
|
||||||
= meta::InstantiateForEach<typename TYPES::List, ValueAcceptInterface>;
|
|
||||||
|
|
||||||
|
|
||||||
}//(End) implementation helpers
|
|
||||||
|
|
||||||
|
using EmptyBase = struct{};
|
||||||
|
|
||||||
|
template<class IFA, class BASE = EmptyBase>
|
||||||
/**
|
class VirtualCopySupportInterface
|
||||||
* Typesafe union record.
|
: public BASE
|
||||||
* 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<typename TYPES>
|
|
||||||
class Variant
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum { SIZ = meta::maxSize<typename TYPES::List>::value };
|
virtual void copyInto (void* targetStorage) const =0;
|
||||||
|
virtual void moveInto (void* targetStorage) =0;
|
||||||
class Visitor
|
virtual void copyInto (IFA& target) const =0;
|
||||||
: public VisitorInterface<TYPES>
|
virtual void moveInto (IFA& target) =0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template<class I, class D, class B =I>
|
||||||
|
class NoCopyMoveSupport
|
||||||
|
: public B
|
||||||
|
{
|
||||||
|
virtual void
|
||||||
|
copyInto (void*) const override
|
||||||
{
|
{
|
||||||
public:
|
throw error::Logic("Copy construction invoked but target is noncopyable");
|
||||||
virtual ~Visitor() { } ///< this is an interface
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
/** Inner capsule managing the contained object (interface) */
|
|
||||||
struct Buffer
|
|
||||||
: VirtualCopySupportInterface<Buffer>
|
|
||||||
{
|
|
||||||
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<typename TY>
|
|
||||||
struct Buff
|
|
||||||
: CopySupport<TY>::template Policy<Buffer,Buff<TY>>
|
|
||||||
{
|
|
||||||
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<TY*> (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<Buff*> (&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<TY>& 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<Buffer*> (&storage_);
|
|
||||||
}
|
|
||||||
Buffer const&
|
|
||||||
buffer() const
|
|
||||||
{
|
|
||||||
return *reinterpret_cast<const Buffer*> (&storage_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename X>
|
virtual void
|
||||||
Buff<X>&
|
moveInto (void*) override
|
||||||
buff()
|
|
||||||
{
|
{
|
||||||
return Buff<X>::downcast(this->buffer());
|
throw error::Logic("Move construction invoked but target is noncopyable");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void
|
||||||
public:
|
copyInto (I&) const override
|
||||||
~Variant()
|
|
||||||
{
|
{
|
||||||
buffer().~Buffer();
|
throw error::Logic("Assignment invoked but target is not assignable");
|
||||||
}
|
}
|
||||||
|
|
||||||
Variant()
|
virtual void
|
||||||
|
moveInto (I&) override
|
||||||
{
|
{
|
||||||
using DefaultType = typename TYPES::List::Head;
|
throw error::Logic("Assignment invoked but target is not assignable");
|
||||||
|
}
|
||||||
new(storage_) Buff<DefaultType> (DefaultType());
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<class I, class D, class B =I>
|
||||||
|
class MoveSupport
|
||||||
|
: public NoCopyMoveSupport<I,D,B>
|
||||||
|
{
|
||||||
|
virtual void
|
||||||
|
copyInto (void*) const override
|
||||||
|
{
|
||||||
|
throw error::Logic("Copy construction invoked but target allows only move construction");
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename X>
|
virtual void
|
||||||
Variant(X&& x)
|
moveInto (void* targetStorage) override
|
||||||
{
|
{
|
||||||
using StorageType = typename CanBuildFrom<X, TYPES>::Type;
|
D& src = static_cast<D&> (*this);
|
||||||
|
new(targetStorage) D(move(src));
|
||||||
new(storage_) Buff<StorageType> (forward<X>(x));
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<class I, class D, class B =I>
|
||||||
|
class CloneSupport
|
||||||
|
: public MoveSupport<I,D,B>
|
||||||
|
{
|
||||||
|
virtual void
|
||||||
|
copyInto (void* targetStorage) const override
|
||||||
|
{
|
||||||
|
D const& src = static_cast<D const&> (*this);
|
||||||
|
new(targetStorage) D(src);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<class I, class D, class B =I>
|
||||||
|
class FullCopySupport
|
||||||
|
: public CloneSupport<I,D,B>
|
||||||
|
{
|
||||||
|
virtual void
|
||||||
|
copyInto (I& target) const override
|
||||||
|
{
|
||||||
|
D& t = D::downcast(target);
|
||||||
|
D const& s = static_cast<D const&> (*this);
|
||||||
|
t = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
Variant (Variant& ref)
|
virtual void
|
||||||
|
moveInto (I& target) override
|
||||||
{
|
{
|
||||||
ref.buffer().copyInto (&storage_);
|
D& t = D::downcast(target);
|
||||||
}
|
D& s = static_cast<D&> (*this);
|
||||||
|
t = move(s);
|
||||||
Variant (Variant const& ref)
|
|
||||||
{
|
|
||||||
ref.buffer().copyInto (&storage_);
|
|
||||||
}
|
|
||||||
|
|
||||||
Variant (Variant&& rref)
|
|
||||||
{
|
|
||||||
rref.buffer().moveInto (&storage_);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename X>
|
|
||||||
Variant&
|
|
||||||
operator= (X x)
|
|
||||||
{
|
|
||||||
using RawType = typename remove_reference<X>::type;
|
|
||||||
static_assert (meta::isInList<RawType, typename TYPES::List>(),
|
|
||||||
"Type error: the given variant could never hold the required type");
|
|
||||||
static_assert (can_use_assignment<RawType>::value, "target type does not support assignment");
|
|
||||||
|
|
||||||
buff<RawType>() = forward<X>(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<typename X>
|
|
||||||
X&
|
|
||||||
get()
|
|
||||||
{
|
|
||||||
static_assert (meta::isInList<X, typename TYPES::List>(),
|
|
||||||
"Type error: the given variant could never hold the required type");
|
|
||||||
|
|
||||||
return buff<X>().access();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
accept (Visitor& visitor)
|
|
||||||
{
|
|
||||||
buffer().dispatch (visitor);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace lib
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* == diagnostic helper == */
|
|
||||||
|
|
||||||
#ifdef LIB_FORMAT_UTIL_H
|
|
||||||
namespace lib {
|
|
||||||
|
|
||||||
template<typename TYPES>
|
/** workaround for GCC 4.7: need to exclude some types,
|
||||||
Variant<TYPES>::operator string() const
|
* since they raise private access violation during probing.
|
||||||
{
|
* Actually, in C++11 such a case should trigger substitution
|
||||||
return string(buffer());
|
* failure, not an compilation error */
|
||||||
}
|
template<class X>
|
||||||
|
struct can_use_assignment
|
||||||
|
: is_copy_assignable<X>
|
||||||
|
{ };
|
||||||
|
|
||||||
template<typename TYPES>
|
template<>
|
||||||
template<typename TY>
|
struct can_use_assignment<lib::time::Time>
|
||||||
Variant<TYPES>::Buff<TY>::operator string() const
|
{ static constexpr bool value = false; };
|
||||||
{
|
|
||||||
return "Variant|"
|
|
||||||
+ util::str (this->access(),
|
|
||||||
(util::tyStr<TY>()+"|").c_str()
|
template<class X>
|
||||||
);
|
struct use_if_supports_only_move
|
||||||
}
|
: enable_if< is_move_constructible<X>::value
|
||||||
}// namespace lib
|
&& !is_copy_constructible<X>::value
|
||||||
|
&& !can_use_assignment<X>::value
|
||||||
#endif
|
>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template<class X>
|
||||||
|
struct use_if_supports_cloning
|
||||||
|
: enable_if< is_move_constructible<X>::value
|
||||||
|
&& is_copy_constructible<X>::value
|
||||||
|
&& !can_use_assignment<X>::value
|
||||||
|
>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
template<class X>
|
||||||
|
struct use_if_supports_copy_and_assignment
|
||||||
|
: enable_if< is_move_constructible<X>::value
|
||||||
|
&& is_copy_constructible<X>::value
|
||||||
|
&& can_use_assignment<X>::value
|
||||||
|
>
|
||||||
|
{ };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** Policy to pick a suitable implementation of "virtual copy operations".
|
||||||
|
* @note You need to mix in the VirtualCopySupportInterface
|
||||||
|
*/
|
||||||
|
template<class X, class SEL=void>
|
||||||
|
struct CopySupport
|
||||||
|
{
|
||||||
|
template<class I, class D, class B =I>
|
||||||
|
using Policy = NoCopyMoveSupport<I,D,B>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class X>
|
||||||
|
struct CopySupport<X, typename use_if_supports_only_move<X>::type>
|
||||||
|
{
|
||||||
|
template<class I, class D, class B =I>
|
||||||
|
using Policy = MoveSupport<I,D,B>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class X>
|
||||||
|
struct CopySupport<X, typename use_if_supports_cloning<X>::type>
|
||||||
|
{
|
||||||
|
template<class I, class D, class B =I>
|
||||||
|
using Policy = CloneSupport<I,D,B>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class X>
|
||||||
|
struct CopySupport<X, typename use_if_supports_copy_and_assignment<X>::type>
|
||||||
|
{
|
||||||
|
template<class I, class D, class B =I>
|
||||||
|
using Policy = FullCopySupport<I,D,B>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}} // namespace lib::meta
|
||||||
#endif /*LIB_META_VIRTUAL_COPY_SUPPORT_H*/
|
#endif /*LIB_META_VIRTUAL_COPY_SUPPORT_H*/
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@
|
||||||
**
|
**
|
||||||
** @see Veriant_test
|
** @see Veriant_test
|
||||||
** @see lib::diff::GenNode
|
** @see lib::diff::GenNode
|
||||||
|
** @see virtual-copy-support.hpp
|
||||||
**
|
**
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -72,8 +73,11 @@
|
||||||
#include "lib/meta/typelist.hpp"
|
#include "lib/meta/typelist.hpp"
|
||||||
#include "lib/meta/typelist-util.hpp"
|
#include "lib/meta/typelist-util.hpp"
|
||||||
#include "lib/meta/generator.hpp"
|
#include "lib/meta/generator.hpp"
|
||||||
|
#include "lib/meta/virtual-copy-support.hpp"
|
||||||
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
namespace lib {
|
namespace lib {
|
||||||
|
|
@ -89,12 +93,7 @@ namespace lib {
|
||||||
|
|
||||||
namespace { // implementation helpers
|
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::remove_reference;
|
||||||
using std::enable_if;
|
|
||||||
using meta::NullType;
|
using meta::NullType;
|
||||||
using meta::Node;
|
using meta::Node;
|
||||||
|
|
||||||
|
|
@ -126,9 +125,18 @@ namespace lib {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template<class VAL>
|
||||||
|
struct ValueAcceptInterface
|
||||||
|
{
|
||||||
|
virtual void handle(VAL&) { /* NOP */ };
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename TYPES>
|
||||||
|
using VisitorInterface
|
||||||
|
= meta::InstantiateForEach<typename TYPES::List, ValueAcceptInterface>;
|
||||||
|
|
||||||
|
|
||||||
/////TODO: *nur noch*
|
/////TODO: *nur noch*
|
||||||
/////TODO: - CopySuport in eigenen Header
|
|
||||||
/////TODO: - diesen mit Unit-Test covern
|
|
||||||
/////TODO: - den gefährlichen Cast aus AccessCasted weg
|
/////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: - *nur* der Unit-Test für OpaqueBuffer ist betroffen. Diesen durch direkten statischen Cast ersetzen
|
||||||
/////TODO: - unit-Test für AccessCasted nachliefern
|
/////TODO: - unit-Test für AccessCasted nachliefern
|
||||||
|
|
@ -168,7 +176,7 @@ namespace lib {
|
||||||
private:
|
private:
|
||||||
/** Inner capsule managing the contained object (interface) */
|
/** Inner capsule managing the contained object (interface) */
|
||||||
struct Buffer
|
struct Buffer
|
||||||
: VirtualCopySupportInterface<Buffer>
|
: meta::VirtualCopySupportInterface<Buffer>
|
||||||
{
|
{
|
||||||
char content_[SIZ];
|
char content_[SIZ];
|
||||||
|
|
||||||
|
|
@ -347,7 +355,7 @@ namespace lib {
|
||||||
using RawType = typename remove_reference<X>::type;
|
using RawType = typename remove_reference<X>::type;
|
||||||
static_assert (meta::isInList<RawType, typename TYPES::List>(),
|
static_assert (meta::isInList<RawType, typename TYPES::List>(),
|
||||||
"Type error: the given variant could never hold the required type");
|
"Type error: the given variant could never hold the required type");
|
||||||
static_assert (can_use_assignment<RawType>::value, "target type does not support assignment");
|
static_assert (meta::can_use_assignment<RawType>::value, "target type does not support assignment");
|
||||||
|
|
||||||
buff<RawType>() = forward<X>(x);
|
buff<RawType>() = forward<X>(x);
|
||||||
return *this;
|
return *this;
|
||||||
|
|
|
||||||
|
|
@ -771,3 +771,7 @@ END
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TEST "Virtual copying of opaque objects" VirtualCopySupport_test <<END
|
||||||
|
return: 0
|
||||||
|
END
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,8 @@ namespace test {
|
||||||
*/
|
*/
|
||||||
class TypeListGenerator_test : public Test
|
class TypeListGenerator_test : public Test
|
||||||
{
|
{
|
||||||
virtual void run(Arg)
|
virtual void
|
||||||
|
run (Arg)
|
||||||
{
|
{
|
||||||
NumberBabbler me_can_has_more_numberz;
|
NumberBabbler me_can_has_more_numberz;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
VirtualCopySupport(Test) - copy and clone type-erased objects
|
VirtualCopySupport(Test) - copy and clone type-erased objects
|
||||||
|
|
||||||
Copyright (C) Lumiera.org
|
Copyright (C) Lumiera.org
|
||||||
2012, Hermann Vosseler <Ichthyostega@web.de>
|
2015, Hermann Vosseler <Ichthyostega@web.de>
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or
|
This program is free software; you can redistribute it and/or
|
||||||
modify it under the terms of the GNU General Public License as
|
modify it under the terms of the GNU General Public License as
|
||||||
|
|
@ -38,6 +38,9 @@ using std::string;
|
||||||
using std::cout;
|
using std::cout;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
|
|
||||||
|
using lumiera::error::LUMIERA_ERROR_LOGIC;
|
||||||
|
using lumiera::error::LUMIERA_ERROR_WRONG_TYPE;
|
||||||
|
|
||||||
|
|
||||||
namespace lib {
|
namespace lib {
|
||||||
namespace meta {
|
namespace meta {
|
||||||
|
|
@ -61,7 +64,30 @@ namespace test {
|
||||||
|
|
||||||
int _CheckSum_ = 0;
|
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
|
class Interface
|
||||||
|
: public CopyInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual ~Interface() { }
|
virtual ~Interface() { }
|
||||||
|
|
@ -69,6 +95,9 @@ namespace test {
|
||||||
virtual bool empty() const =0;
|
virtual bool empty() const =0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* implementation class with "special" memory layout
|
||||||
|
*/
|
||||||
template<uint i>
|
template<uint i>
|
||||||
class Sub
|
class Sub
|
||||||
: public Interface
|
: public Interface
|
||||||
|
|
@ -88,7 +117,8 @@ namespace test {
|
||||||
return storage_[i-1];
|
return storage_[i-1];
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public: /* == Implementation of the Interface == */
|
||||||
|
|
||||||
virtual operator string() const override
|
virtual operator string() const override
|
||||||
{
|
{
|
||||||
return _Fmt("Sub|%s|%d|-%s")
|
return _Fmt("Sub|%s|%d|-%s")
|
||||||
|
|
@ -103,6 +133,9 @@ namespace test {
|
||||||
return !bool(access());
|
return !bool(access());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public: /* == full set of copy and assignment operations == */
|
||||||
|
|
||||||
~Sub()
|
~Sub()
|
||||||
{
|
{
|
||||||
_CheckSum_ -= access();
|
_CheckSum_ -= access();
|
||||||
|
|
@ -136,6 +169,7 @@ namespace test {
|
||||||
Sub&
|
Sub&
|
||||||
operator= (Sub&& rsub)
|
operator= (Sub&& rsub)
|
||||||
{
|
{
|
||||||
|
_CheckSum_ -= access();
|
||||||
access() = 0;
|
access() = 0;
|
||||||
std::swap(access(),rsub.access());
|
std::swap(access(),rsub.access());
|
||||||
return *this;
|
return *this;
|
||||||
|
|
@ -154,7 +188,7 @@ namespace test {
|
||||||
class UnAssignable
|
class UnAssignable
|
||||||
: public Sub<c>
|
: public Sub<c>
|
||||||
{
|
{
|
||||||
void operator= (UnAssignable const&);
|
void operator= (UnAssignable const&); // private and unimplemented
|
||||||
public:
|
public:
|
||||||
UnAssignable() = default;
|
UnAssignable() = default;
|
||||||
UnAssignable(UnAssignable&&) = default;
|
UnAssignable(UnAssignable&&) = default;
|
||||||
|
|
@ -180,9 +214,50 @@ namespace test {
|
||||||
Noncopyable () = default;
|
Noncopyable () = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* == concrete implementation subclass with virtual copy support == */
|
||||||
|
|
||||||
|
template<class IMP>
|
||||||
|
class Opaque //-----Interface| CRTP-Impl | direct Baseclass
|
||||||
|
: public CopySupport<IMP>::template Policy<Interface, Opaque<IMP>, IMP>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static Opaque&
|
||||||
|
downcast (Interface& bas)
|
||||||
|
{
|
||||||
|
Opaque* impl = dynamic_cast<Opaque*> (&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<Regular<'a'>>;
|
||||||
|
using ClonableImpl = Opaque<UnAssignable<'b'>>;
|
||||||
|
using MovableImpl = Opaque<OnlyMovable<'c'>>;
|
||||||
|
using ImobileImpl = Opaque<Noncopyable<'d'>>;
|
||||||
|
|
||||||
}//(End)Test fixture
|
}//(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<char c>
|
||||||
|
struct can_use_assignment<test::UnAssignable<c>>
|
||||||
|
{ static constexpr bool value = false; };
|
||||||
|
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -203,8 +278,28 @@ namespace test {
|
||||||
verify_TestFixture();
|
verify_TestFixture();
|
||||||
|
|
||||||
CHECK(0 == _CheckSum_);
|
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
|
void
|
||||||
verify_TestFixture()
|
verify_TestFixture()
|
||||||
{
|
{
|
||||||
|
|
@ -221,6 +316,12 @@ namespace test {
|
||||||
|
|
||||||
CHECK (string(a) == string(aa));
|
CHECK (string(a) == string(aa));
|
||||||
CHECK (string(a) == string(a1));
|
CHECK (string(a) == string(a1));
|
||||||
|
CHECK (!isnil(a1));
|
||||||
|
|
||||||
|
a = std::move(a1);
|
||||||
|
|
||||||
|
CHECK (isnil(a1));
|
||||||
|
CHECK (string(a) == string(aa));
|
||||||
|
|
||||||
|
|
||||||
/* == interface vs. concrete class == */
|
/* == interface vs. concrete class == */
|
||||||
|
|
@ -275,6 +376,168 @@ namespace test {
|
||||||
|
|
||||||
CHECK (!isnil (e));
|
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<Interface*> (&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<Regular<'!'>> divergent;
|
||||||
|
Interface& evil(divergent);
|
||||||
|
VERIFY_ERROR (WRONG_TYPE, evil.copyInto(i));
|
||||||
|
VERIFY_ERROR (WRONG_TYPE, evil.moveInto(i));
|
||||||
|
|
||||||
|
|
||||||
|
cout << "==fullVirtualCopySupport=="<<endl
|
||||||
|
<< string(i) <<endl
|
||||||
|
<< string(ii) <<endl
|
||||||
|
<< string(iii) <<endl
|
||||||
|
<< string(iiii)<<endl;
|
||||||
|
|
||||||
|
//need to clean-up the placement-new instance explicitly
|
||||||
|
iiii.~Interface();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
verify_noAssignementSupport()
|
||||||
|
{
|
||||||
|
ClonableImpl b,bb,bbb;
|
||||||
|
Interface& i(b);
|
||||||
|
Interface& ii(bb);
|
||||||
|
Interface& iii(bbb);
|
||||||
|
|
||||||
|
char storage[sizeof(ClonableImpl)];
|
||||||
|
Interface& iiii (*reinterpret_cast<Interface*> (&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=="<<endl
|
||||||
|
<< string(i) <<endl
|
||||||
|
<< string(ii) <<endl
|
||||||
|
<< string(iii) <<endl
|
||||||
|
<< string(iiii)<<endl;
|
||||||
|
|
||||||
|
//clean-up placement-new instance
|
||||||
|
iiii.~Interface();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
verify_onlyMovableSupport()
|
||||||
|
{
|
||||||
|
MovableImpl c,cc;
|
||||||
|
Interface& i(c);
|
||||||
|
Interface& ii(cc);
|
||||||
|
|
||||||
|
char storage[sizeof(MovableImpl)];
|
||||||
|
Interface& iiii (*reinterpret_cast<Interface*> (&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=="<<endl
|
||||||
|
<< string(i) <<endl
|
||||||
|
<< string(ii) <<endl
|
||||||
|
<< string(iiii)<<endl;
|
||||||
|
|
||||||
|
//clean-up placement-new instance
|
||||||
|
iiii.~Interface();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
verify_disabledCopySupport()
|
||||||
|
{
|
||||||
|
ImobileImpl d,dd;
|
||||||
|
Interface& i(d);
|
||||||
|
Interface& ii(dd);
|
||||||
|
char storage[sizeof(ImobileImpl)];
|
||||||
|
|
||||||
|
CHECK (!isnil (i));
|
||||||
|
|
||||||
|
string prevID = ii;
|
||||||
|
VERIFY_ERROR (LOGIC, ii.copyInto(&storage));
|
||||||
|
VERIFY_ERROR (LOGIC, ii.moveInto(&storage));
|
||||||
|
VERIFY_ERROR (LOGIC, ii.copyInto(i));
|
||||||
|
VERIFY_ERROR (LOGIC, ii.moveInto(i));
|
||||||
|
CHECK (string(ii) == prevID);
|
||||||
|
CHECK (!isnil(ii));
|
||||||
|
CHECK (!isnil (i));
|
||||||
|
|
||||||
|
cout << "==disabledCopySupport=="<<endl
|
||||||
|
<< string(i) <<endl
|
||||||
|
<< string(ii) <<endl;
|
||||||
|
|
||||||
|
//no clean-up,
|
||||||
|
//since we never created anything in the storage buffer
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue