WIP: start factoring out the virtual copy support

This commit is contained in:
Fischlurch 2015-04-20 00:49:49 +02:00
parent 5a4290d4a7
commit 67b5df0d1d
3 changed files with 904 additions and 188 deletions

View file

@ -0,0 +1,609 @@
/*
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
** 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.
**
** 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.
**
** \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).
**
** 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.
**
** @see Veriant_test
** @see lib::diff::GenNode
**
*/
#ifndef LIB_META_VIRTUAL_COPY_SUPPORT_H
#define LIB_META_VIRTUAL_COPY_SUPPORT_H
#include "lib/meta/typelist.hpp"
#include "lib/meta/typelist-util.hpp"
#include "lib/meta/generator.hpp"
#include <type_traits>
namespace lib {
using std::string;
using std::move;
using std::forward;
using util::unConst;
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
/**
* 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
{
public:
enum { SIZ = meta::maxSize<typename TYPES::List>::value };
class Visitor
: public VisitorInterface<TYPES>
{
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_);
}
template<typename X>
Buff<X>&
buff()
{
return Buff<X>::downcast(this->buffer());
}
public:
~Variant()
{
buffer().~Buffer();
}
Variant()
{
using DefaultType = typename TYPES::List::Head;
new(storage_) Buff<DefaultType> (DefaultType());
}
template<typename X>
Variant(X&& x)
{
using StorageType = typename CanBuildFrom<X, TYPES>::Type;
new(storage_) Buff<StorageType> (forward<X>(x));
}
Variant (Variant& ref)
{
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);
}
};
} // namespace lib
/* == diagnostic helper == */
#ifdef LIB_FORMAT_UTIL_H
namespace lib {
template<typename TYPES>
Variant<TYPES>::operator string() const
{
return string(buffer());
}
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
#endif /*LIB_META_VIRTUAL_COPY_SUPPORT_H*/

View file

@ -26,7 +26,7 @@
** 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.
@ -126,192 +126,13 @@ namespace lib {
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>;
/////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
/////TODO: - forward-call für Konstruktor im Buffer? aber nur, wenn es sich verifizieren läßt!
}//(End) implementation helpers
@ -364,7 +185,7 @@ namespace lib {
/** concrete inner capsule specialised for a given type */
template<typename TY>
struct Buff
: CopySupport<TY>::template Policy<Buffer,Buff<TY>>
: meta::CopySupport<TY>::template Policy<Buffer,Buff<TY>>
{
static_assert (SIZ >= sizeof(TY), "Variant record: insufficient embedded Buffer size");

View file

@ -0,0 +1,286 @@
/*
VirtualCopySupport(Test) - copy and clone type-erased objects
Copyright (C) Lumiera.org
2012, 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.
* *****************************************************/
#include "lib/test/run.hpp"
#include "lib/format-string.hpp"
#include "lib/test/test-helper.hpp"
#include "lib/meta/virtual-copy-support.hpp"
#include "lib/util.hpp"
#include <iostream>
#include <string>
#include <type_traits>
using util::_Fmt;
using util::isnil;
using std::string;
using std::cout;
using std::endl;
namespace lib {
namespace meta {
namespace test {
namespace { // Test fixture...
class Interface;
/** helper: shortened type display */
string
typeID(Interface const& obj)
{
string typeStr = lib::test::demangleCxx(
lib::test::showType(obj));
size_t pos = typeStr.rfind("::");
if (pos != string::npos)
typeStr = typeStr.substr(pos+2);
return typeStr;
}
int _CheckSum_ = 0;
class Interface
{
public:
virtual ~Interface() { }
virtual operator string() const =0;
virtual bool empty() const =0;
};
template<uint i>
class Sub
: public Interface
{
static_assert (0 < i, "invalid construction");
char storage_[i];
char&
access()
{
return storage_[i-1];
}
char const&
access() const
{
return storage_[i-1];
}
public:
virtual operator string() const override
{
return _Fmt("Sub|%s|%d|-%s")
% typeID(*this)
% i
% access();
}
virtual bool
empty() const
{
return !bool(access());
}
~Sub()
{
_CheckSum_ -= access();
}
Sub()
{
access() = 'A' + rand() % 23;
_CheckSum_ += access();
}
Sub (Sub const& osub)
{
access() = osub.access();
_CheckSum_ += access();
}
Sub (Sub&& rsub)
{
access() = 0;
std::swap(access(),rsub.access());
}
Sub&
operator= (Sub const& osub)
{
if (!util::isSameObject (*this, osub))
{
_CheckSum_ -= access();
access() = osub.access();
_CheckSum_ += access();
}
return *this;
}
Sub&
operator= (Sub&& rsub)
{
access() = 0;
std::swap(access(),rsub.access());
return *this;
}
};
/* == create various flavours of copyable / noncopyable objects == */
template<char c>
class Regular
: public Sub<c>
{ };
template<char c>
class UnAssignable
: public Sub<c>
{
void operator= (UnAssignable const&);
public:
UnAssignable() = default;
UnAssignable(UnAssignable&&) = default;
UnAssignable(UnAssignable const&) = default;
};
template<char c>
class OnlyMovable
: public UnAssignable<c>
{
public:
OnlyMovable (OnlyMovable const&) = delete;
OnlyMovable() = default;
OnlyMovable(OnlyMovable&&) = default;
};
template<char c>
class Noncopyable
: public OnlyMovable<c>
{
public:
Noncopyable (Noncopyable&&) = delete;
Noncopyable () = default;
};
}//(End)Test fixture
/**********************************************************************************************//**
* @test verify a mechanism to allow for cloning and placement new of opaque, type-erased entities.
* Basically we allow only assignments and copy between objects of the same concrete type,
* but we want to initiate those operations from the base interface, without any further
* knowledge about the actual types involved.
*/
class VirtualCopySupport_test : public Test
{
virtual void
run (Arg)
{
CHECK(0 == _CheckSum_);
verify_TestFixture();
CHECK(0 == _CheckSum_);
}
void
verify_TestFixture()
{
/* == full copy, move and assignment == */
Regular<'A'> a;
Regular<'A'> aa(a);
Regular<'A'> a1;
cout << string(a) <<endl
<< string(aa)<<endl
<< string(a1)<<endl;
a1 = a;
CHECK (string(a) == string(aa));
CHECK (string(a) == string(a1));
/* == interface vs. concrete class == */
Regular<'B'> b;
Interface& ii = b;
string prevID(b);
ii = a; // Copy operation of Base-Interface is NOP
CHECK (string(b) == prevID);
/* == assignment inhibited == */
UnAssignable<'C'> c;
UnAssignable<'C'> cc(c);
CHECK (string(c) == string(cc));
prevID = cc;
UnAssignable<'C'> ccc(std::move(cc));
cout << string(cc) <<endl
<< string(ccc)<<endl;
CHECK (string(ccc) == prevID);
CHECK (string(cc) != prevID);
CHECK (!isnil(ccc));
CHECK (isnil (cc)); // killed by moving away
// c = cc; // does not compile
/* == only move construction allowed == */
OnlyMovable<'D'> d;
OnlyMovable<'D'> dd (std::move(d));
cout << string(d) <<endl
<< string(dd)<<endl;
CHECK (string(dd) != string(d));
CHECK (!isnil(dd));
CHECK (isnil(d));
// OnlyMovable<'D'> d1 (dd); // does not compile
// d = dd; // does not compile;
/* == all copy/assignment inhibited == */
Noncopyable<'E'> e;
// Noncopyable<'E'> ee (e); // does not compile
// Noncopyable<'E'> eee (std::move (e)); // does not compile
// e = Noncopyable<'E'>(); // does not compile
CHECK (!isnil (e));
}
};
/** Register this test class... */
LAUNCHER (VirtualCopySupport_test, "unit common");
}}} // namespace lib::meta::test