LUMIERA.clone/src/lib/variant.hpp

582 lines
16 KiB
C++
Raw Normal View History

2015-04-16 23:04:36 +02:00
/*
VARIANT.hpp - lightweight typesafe union record
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 variant.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.
**
** \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).
**
** @see Veriant_test
** @see lib::diff::GenNode
**
*/
#ifndef LIB_VARIANT_H
#define LIB_VARIANT_H
#include "lib/meta/typelist.hpp"
#include "lib/meta/typelist-util.hpp"
//#include "lib/util.hpp"
#include <type_traits>
#include <typeindex>
2015-04-16 23:04:36 +02:00
//#include <utility>
//#include <string>
//#include <array>
namespace lib {
using std::string;
using std::move;
using std::forward;
2015-04-16 23:04:36 +02:00
using util::unConst;
namespace error = lumiera::error;
template<typename TYPES>
class Variant;
namespace { // implementation helpers
using std::is_constructible;
using std::remove_reference;
using std::remove_const;
using std::is_same;
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");
};
template<typename T>
struct Bare
{
using Type = typename remove_const<
typename remove_reference<T>::type>::type;
};
template<class T>
struct is_Variant
{
static constexpr bool value = false;
};
template<class TYPES>
struct is_Variant<Variant<TYPES>>
{
static constexpr bool value = true;
};
template<class V>
struct use_if_is_Variant
: enable_if<is_Variant<typename Bare<V>::Type>::value, V>
{ };
template<class B, class D>
class FullCopySupport
: public B
{
virtual void
copyInto (void* targetStorage) const override
{
D const& src = static_cast<D const&> (*this);
new(targetStorage) D(src);
}
virtual void
moveInto (void* targetStorage) override
{
D& src = static_cast<D&> (*this);
new(targetStorage) D(move(src));
}
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);
}
};
template<class B, class D>
class CloneSupport
: public B
{
virtual void
copyInto (void* targetStorage) const override
{
D const& src = static_cast<D const&> (*this);
new(targetStorage) D(src);
}
virtual void
moveInto (void* targetStorage) override
{
D& src = static_cast<D&> (*this);
new(targetStorage) D(move(src));
}
virtual void
copyInto (B& target) const override
{
throw error::Logic("Assignment invoked but target is not assignable");
}
virtual void
moveInto (B& target) override
{
throw error::Logic("Assignment invoked but target is not assignable");
}
};
template<class B, class D>
class MoveSupport
: public B
{
virtual void
copyInto (void* targetStorage) 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));
}
virtual void
copyInto (B& target) const override
{
throw error::Logic("Assignment invoked but target is not assignable");
}
virtual void
moveInto (B& target) override
{
throw error::Logic("Assignment invoked but target is not assignable");
}
};
template<class B, class D>
class NoCopyMoveSupport
: public B
{
virtual void
copyInto (void* targetStorage) const override
{
throw error::Logic("Copy construction invoked but target is noncopyable");
}
virtual void
moveInto (void* targetStorage) override
{
throw error::Logic("Move construction invoked but target is noncopyable");
}
virtual void
copyInto (B& target) const override
{
throw error::Logic("Assignment invoked but target is not assignable");
}
virtual void
moveInto (B& target) override
{
throw error::Logic("Assignment invoked but target is not assignable");
}
};
template<class X, class SEL=void>
struct CopySupport
{
template<class B, class D>
using Policy = NoCopyMoveSupport<B,D>;
};
}//(End) implementation helpers
2015-04-16 23:04:36 +02:00
/**
* 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.
*/
template<typename TYPES>
class Variant
{
enum { SIZ = meta::maxSize<typename TYPES::List>::value };
/** Inner capsule managing the contained object (interface) */
struct Buffer
{
char content_[SIZ];
void* ptr() { return &content_; }
virtual ~Buffer() {} ///< this is an ABC with VTable
virtual void copyInto (void* targetStorage) const =0;
virtual void moveInto (void* targetStorage) =0;
virtual void copyInto (Buffer& target) const =0;
virtual void moveInto (Buffer& target) =0;
// virtual void operator= (Buffer const&) =0;
// virtual void operator= (Buffer&&) =0;
2015-04-16 23:04:36 +02:00
};
/** concrete inner capsule specialised for a given type */
template<typename TY>
struct Buff
: CopySupport<TY>::template Policy<Buffer,Buff<TY>>
2015-04-16 23:04:36 +02:00
{
static_assert (SIZ >= sizeof(TY), "Variant record: insufficient embedded Buffer size");
TY&
access() const ///< core operation: target is contained within the inline buffer
2015-04-16 23:04:36 +02:00
{
return *reinterpret_cast<TY*> (unConst(this)->ptr());
}
~Buff()
{
access().~TY();
2015-04-16 23:04:36 +02:00
}
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());
2015-04-16 23:04:36 +02:00
}
Buff (Buff && rBuff)
{
new(Buffer::ptr()) TY(move (rBuff.access()));
2015-04-16 23:04:36 +02:00
}
void
assignValue (TY const& ob)
2015-04-16 23:04:36 +02:00
{
if (&ob != Buffer::ptr())
this->access() = ob;
2015-04-16 23:04:36 +02:00
}
void
assignValue (TY && rob)
2015-04-16 23:04:36 +02:00
{
this->access() = move(rob);
2015-04-16 23:04:36 +02:00
}
/* == virtual access functions == */
virtual void
copyInto (void* targetStorage) const override
{
new(targetStorage) Buff(this->access());
}
// virtual void
// operator= (Buffer const& ob) override
// {
// assignValue (downcast(unConst(ob)).access());
// }
//
// virtual void
// operator= (Buffer&& rref) override
// {
// assignValue (move (downcast(rref).access()));
// }
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;
2015-04-16 23:04:36 +02:00
}
};
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];
template <class X, class SEL=void>
struct Assign
{
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 void
assign (Buffer& buffer, X&& x)
{
buff<RawType>().assignValue (forward<X>(x));
}
};
template<class V>
struct Assign<V, typename use_if_is_Variant<V>::type>
{
static void
assign (Buffer& buffer, V&& oVar)
{
// oVar.buffer()
// buff<RawType>().assignValue (forward<X>(oVar));
}
};
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());
}
2015-04-16 23:04:36 +02:00
public:
~Variant()
{
buffer().~Buffer();
}
2015-04-16 23:04:36 +02:00
Variant()
{
using DefaultType = typename TYPES::List::Head;
new(storage_) Buff<DefaultType> (DefaultType());
}
Variant (Variant& ref)
{
ref.buffer().copyInto (&storage_);
}
Variant (Variant const& ref)
{
ref.buffer().copyInto (&storage_);
}
Variant (Variant&& rref)
{
rref.buffer().copyInto (&storage_);
2015-04-16 23:04:36 +02:00
}
template<typename X>
Variant(X&& x)
2015-04-16 23:04:36 +02:00
{
using StorageType = typename CanBuildFrom<X, TYPES>::Type;
new(storage_) Buff<StorageType> (forward<X>(x));
2015-04-16 23:04:36 +02:00
}
template<typename X>
Variant&
operator= (X x)
2015-04-16 23:04:36 +02:00
{
// 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");
//
// buff<RawType>().assignValue (forward<X>(x));
Assign<X>::assign (this->buffer(), forward<X> (x));
return *this;
}
/*
Variant&
operator= (Variant& ovar)
{
buffer() = ovar.buffer();
return *this;
}
Variant&
operator= (Variant const& ovar)
{
buffer() = ovar.buffer();
return *this;
}
Variant&
operator= (Variant&& rvar)
{
buffer().operator= (move(rvar.buffer()));
return *this;
2015-04-16 23:04:36 +02:00
}
*/
2015-04-16 23:04:36 +02:00
#ifdef LIB_TEST_TEST_HELPER_H
/* == diagnostic helper == */
operator string() const
{
UNIMPLEMENTED("diagnostic string conversion");
}
#endif
/* === 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();
2015-04-16 23:04:36 +02:00
}
class Visitor
{
public:
virtual ~Visitor() { } ///< this is an interface
};
void
accept (Visitor& visitor)
{
UNIMPLEMENTED("visitor style value access");
}
};
} // namespace lib
#endif /*LIB_VARIANT_H*/