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
|
||||
** 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 <b>not threadsafe</b>.
|
||||
**
|
||||
** 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 <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
|
||||
** 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<TYPES...> 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<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
|
||||
|
||||
|
||||
#include "lib/meta/typelist.hpp"
|
||||
#include "lib/meta/typelist-util.hpp"
|
||||
#include "lib/meta/generator.hpp"
|
||||
#include "lib/error.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <utility>
|
||||
|
||||
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<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{};
|
||||
|
||||
|
||||
/**
|
||||
* 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<typename TYPES>
|
||||
class Variant
|
||||
template<class IFA, class BASE = EmptyBase>
|
||||
class VirtualCopySupportInterface
|
||||
: public BASE
|
||||
{
|
||||
public:
|
||||
enum { SIZ = meta::maxSize<typename TYPES::List>::value };
|
||||
|
||||
class Visitor
|
||||
: public VisitorInterface<TYPES>
|
||||
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 I, class D, class B =I>
|
||||
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<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_);
|
||||
throw error::Logic("Copy construction invoked but target is noncopyable");
|
||||
}
|
||||
|
||||
template<typename X>
|
||||
Buff<X>&
|
||||
buff()
|
||||
virtual void
|
||||
moveInto (void*) override
|
||||
{
|
||||
return Buff<X>::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> (DefaultType());
|
||||
throw error::Logic("Assignment invoked but target is not assignable");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
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>
|
||||
Variant(X&& x)
|
||||
virtual void
|
||||
moveInto (void* targetStorage) override
|
||||
{
|
||||
using StorageType = typename CanBuildFrom<X, TYPES>::Type;
|
||||
|
||||
new(storage_) Buff<StorageType> (forward<X>(x));
|
||||
D& src = static_cast<D&> (*this);
|
||||
new(targetStorage) D(move(src));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
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_);
|
||||
}
|
||||
|
||||
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);
|
||||
D& t = D::downcast(target);
|
||||
D& s = static_cast<D&> (*this);
|
||||
t = move(s);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace lib
|
||||
|
||||
|
||||
|
||||
/* == diagnostic helper == */
|
||||
|
||||
#ifdef LIB_FORMAT_UTIL_H
|
||||
namespace lib {
|
||||
|
||||
template<typename TYPES>
|
||||
Variant<TYPES>::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<class X>
|
||||
struct can_use_assignment
|
||||
: is_copy_assignable<X>
|
||||
{ };
|
||||
|
||||
template<typename TYPES>
|
||||
template<typename TY>
|
||||
Variant<TYPES>::Buff<TY>::operator string() const
|
||||
{
|
||||
return "Variant|"
|
||||
+ util::str (this->access(),
|
||||
(util::tyStr<TY>()+"|").c_str()
|
||||
);
|
||||
}
|
||||
}// namespace lib
|
||||
|
||||
#endif
|
||||
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
|
||||
>
|
||||
{ };
|
||||
|
||||
|
||||
|
||||
|
||||
/** 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*/
|
||||
|
|
|
|||
|
|
@ -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 <type_traits>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
|
||||
|
||||
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<class VAL>
|
||||
struct ValueAcceptInterface
|
||||
{
|
||||
virtual void handle(VAL&) { /* NOP */ };
|
||||
};
|
||||
|
||||
template<typename TYPES>
|
||||
using VisitorInterface
|
||||
= meta::InstantiateForEach<typename TYPES::List, ValueAcceptInterface>;
|
||||
|
||||
|
||||
/////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<Buffer>
|
||||
: meta::VirtualCopySupportInterface<Buffer>
|
||||
{
|
||||
char content_[SIZ];
|
||||
|
||||
|
|
@ -347,7 +355,7 @@ namespace lib {
|
|||
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");
|
||||
static_assert (meta::can_use_assignment<RawType>::value, "target type does not support assignment");
|
||||
|
||||
buff<RawType>() = forward<X>(x);
|
||||
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
|
||||
{
|
||||
virtual void run(Arg)
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
NumberBabbler me_can_has_more_numberz;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
VirtualCopySupport(Test) - copy and clone type-erased objects
|
||||
|
||||
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
|
||||
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<uint i>
|
||||
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<c>
|
||||
{
|
||||
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 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
|
||||
|
||||
|
||||
// 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();
|
||||
|
||||
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<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