Benefits - get rid of yet another pervasive Boost dependency - define additional more fine grained policies (move only, clonable)
304 lines
9.7 KiB
C++
304 lines
9.7 KiB
C++
/*
|
|
VIRTUAL-COPY-SUPPORT.hpp - helper to support copying of erased sub-types
|
|
|
|
Copyright (C) Lumiera.org
|
|
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
|
|
published by the Free Software Foundation; either version 2 of
|
|
the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
*/
|
|
|
|
|
|
/** @file virtual-copy-support.hpp
|
|
** 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.
|
|
**
|
|
** 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 set up 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.
|
|
**
|
|
** 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 cases of
|
|
** - full copy support
|
|
** - copy construction but no assignment
|
|
** - only move construction allowed
|
|
** - non-copyable 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).
|
|
**
|
|
** \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.
|
|
**
|
|
** \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
|
|
**
|
|
*/
|
|
|
|
|
|
#ifndef LIB_META_VIRTUAL_COPY_SUPPORT_H
|
|
#define LIB_META_VIRTUAL_COPY_SUPPORT_H
|
|
|
|
|
|
#include "lib/error.hpp"
|
|
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
|
|
namespace lib {
|
|
namespace meta{
|
|
|
|
using std::move;
|
|
|
|
using std::is_move_constructible;
|
|
using std::is_copy_constructible;
|
|
using std::is_copy_assignable;
|
|
using std::__and_;
|
|
using std::__not_;
|
|
|
|
namespace error = lumiera::error;
|
|
|
|
|
|
|
|
struct EmptyBase { };
|
|
|
|
template<class IFA, class BASE = EmptyBase>
|
|
class VirtualCopySupportInterface
|
|
: public BASE
|
|
{
|
|
public:
|
|
virtual ~VirtualCopySupportInterface() { }
|
|
|
|
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
|
|
{
|
|
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 (I&) const override
|
|
{
|
|
throw error::Logic("Assignment invoked but target is not assignable");
|
|
}
|
|
|
|
virtual void
|
|
moveInto (I&) override
|
|
{
|
|
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");
|
|
}
|
|
|
|
virtual void
|
|
moveInto (void* targetStorage) override
|
|
{
|
|
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;
|
|
}
|
|
|
|
virtual void
|
|
moveInto (I& target) override
|
|
{
|
|
D& t = D::downcast(target);
|
|
D& s = static_cast<D&> (*this);
|
|
t = move(s);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
namespace { // helpers to select suitable variant of copy support...
|
|
|
|
template<class X>
|
|
struct supports_only_move
|
|
: __and_<is_move_constructible<X>
|
|
,__not_<is_copy_constructible<X>>
|
|
,__not_<is_copy_assignable<X>>
|
|
>
|
|
{ };
|
|
|
|
template<class X>
|
|
struct supports_cloning
|
|
: __and_<is_move_constructible<X>
|
|
,is_copy_constructible<X>
|
|
,__not_<is_copy_assignable<X>>
|
|
>
|
|
{ };
|
|
|
|
template<class X>
|
|
struct supports_copy_and_assignment
|
|
: __and_<is_move_constructible<X>
|
|
,is_copy_constructible<X>
|
|
,is_copy_assignable<X>
|
|
>
|
|
{ };
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 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, enable_if<supports_only_move<X>> >
|
|
{
|
|
template<class I, class D, class B =I>
|
|
using Policy = MoveSupport<I,D,B>;
|
|
};
|
|
|
|
template<class X>
|
|
struct CopySupport<X, enable_if<supports_cloning<X>> >
|
|
{
|
|
template<class I, class D, class B =I>
|
|
using Policy = CloneSupport<I,D,B>;
|
|
};
|
|
|
|
template<class X>
|
|
struct CopySupport<X, enable_if<supports_copy_and_assignment<X>> >
|
|
{
|
|
template<class I, class D, class B =I>
|
|
using Policy = FullCopySupport<I,D,B>;
|
|
};
|
|
|
|
|
|
|
|
|
|
}} // namespace lib::meta
|
|
#endif /*LIB_META_VIRTUAL_COPY_SUPPORT_H*/
|