virtual copy support documented and covered with unit test

This commit is contained in:
Fischlurch 2015-04-20 03:41:28 +02:00
parent 67b5df0d1d
commit de50bf7c91
5 changed files with 532 additions and 547 deletions

View file

@ -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*/

View file

@ -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;

View file

@ -771,3 +771,7 @@ END
TEST "Virtual copying of opaque objects" VirtualCopySupport_test <<END
return: 0
END

View file

@ -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;

View file

@ -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
}
};