lumiera_/src/lib/wrapper.hpp
Ichthyostega a90b9e5f16 Library: uniform definition scheme for error-IDs
In the Lumiera code base, we use C-String constants as unique error-IDs.
Basically this allows to create new unique error IDs anywhere in the code.

However, definition of such IDs in arbitrary namespaces tends to create
slight confusion and ambiguities, while maintaining the proper use statements
requires some manual work.

Thus I introduce a new **standard scheme**
 * Error-IDs for widespread use shall be defined _exclusively_ into `namespace lumiera::error`
 * The shorthand-Macro `LERR_()` can now be used to simplify inclusion and referral
 * (for local or single-usage errors, a local or even hidden definition is OK)
2024-03-21 19:57:34 +01:00

450 lines
12 KiB
C++

/*
WRAPPER.hpp - some smart wrapping and reference managing helper templates
Copyright (C) Lumiera.org
2009, 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 wrapper.hpp
** Library implementation: smart-pointer variations, wrappers and managing holders.
** This is (intended to become) a loose collection of the various small helper templates
** for wrapping, containing, placing or handling a somewhat \em problematic other object.
** Mostly these are implemented to suit a specific need and then factored out later on.
** - ItemWrapper is a similar concept, but more like a smart-ptr. Moreover,
** it can be instantiated with a value type, a pointer or a reference type,
** yielding the same behaviour in all cases (useful for building templates)
** - FunctionResult is the combination of ItemWrapper with a functor object
** to cache the function result value.
**
** @see lib::TransformIter
**
** @todo 2017 consider to split off the FunctionResult into a dedicated header to reduce includes
**
*/
#ifndef LIB_WRAPPER_H
#define LIB_WRAPPER_H
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/meta/function.hpp"
#include "lib/meta/function-closure.hpp"
#include "lib/meta/util.hpp"
#include "lib/util.hpp"
#include <functional>
#include <cstddef>
namespace lib {
namespace wrapper {
using util::unConst;
using util::isSameObject;
using lib::meta::_Fun;
using LERR_(BOTTOM_VALUE);
using std::function;
/**
* Reference wrapper implemented as constant function,
* returning the (fixed) reference on invocation
*/
template<typename T>
class ReturnRef
{
T& ref_;
public:
ReturnRef(T& target) : ref_(target) { }
T& operator() () const { return ref_;}
};
template<typename T>
ReturnRef<T>
refFunction (T& target)
{
return ReturnRef<T> (target);
}
/**
* Universal value/ref wrapper accessible similar to a pointer.
* A copyable, assignable value object to hold a given value within
* an embedded inline buffer. It can be default constructed and `bool`
* evaluated to detect an empty holder. The value is retrieved through
* a pointer-like interface, by explicit dereferentiation. (Contrast this
* to std::ref, where the original reference is retrieved by conversion).
* @note when the embedded value is a pointer, ItemWrapper does _not_ take
* ownership of and manage data the pointer is pointing to.
*
* The purpose of this template is to be able to remember
* pretty much any kind of value or pointer or reference,
* and to subsume this handling within a single template.
* An example would be to remember the value yielded
* by a function, without any further assumptions
* regarding this function.
*/
template<typename TY>
class ItemWrapper
{
using TY_unconst = std::remove_const_t<TY>;
alignas(TY) mutable
std::byte content_[sizeof(TY)];
bool created_;
template<typename REF>
void
build (REF&& ref)
{
new(&content_) TY{std::forward<REF> (ref)};
created_ = true;
}
void
discard ()
{
if (created_) access().~TY();
created_ = false;
}
TY&
access () const
{
return * std::launder (reinterpret_cast<TY*> (&content_));
}
TY_unconst&
access_unconst() const ///< used to assign new buffer contents
{
return const_cast<TY_unconst&> (access());
}
public:
ItemWrapper()
: created_(false)
{ }
explicit
ItemWrapper(TY const& o)
: created_(false)
{
build (o);
}
explicit
ItemWrapper(TY && ro)
: created_(false)
{
build (std::move(ro));
}
~ItemWrapper()
{
discard();
}
/* == copy and assignment == */
ItemWrapper (ItemWrapper const& ref)
: created_(false)
{
if (ref.isValid())
build (*ref);
}
ItemWrapper (ItemWrapper && rref)
: created_(false)
{
if (rref.isValid())
{
build (std::move (*rref));
rref.discard();
}
}
ItemWrapper&
operator= (ItemWrapper const& cref)
{
if (!cref.isValid())
discard();
else
this->operator= (*cref);
return *this;
}
ItemWrapper&
operator= (ItemWrapper & ref)
{
return *this = (ItemWrapper const&)ref;
}
ItemWrapper&
operator= (ItemWrapper && rref)
{
if (!rref.isValid())
discard();
else
{
this->operator= (std::move(*rref));
rref.discard();
}
return *this;
}
/**
* Emulate »assignment« by discarding and then construction of a new payload
* @param something from which TY can be (copy/move)constructed
* @remark allows handling »move-only« types; for the typical use case, something
* new is fabricated in a lambda and then moved into the ItemWrapper; thus
* the performance overhead of destroy/re-created is not deemed relevant.
* SFINAE tricks on hidden/deleted assignment operator can be unreliable.
*/
template<typename X>
ItemWrapper&
operator= (X&& something)
{
if (!isSameObject (something, access() ))
{
discard();
build (std::forward<X>(something));
}
return *this;
}
/** implant a default-initialised instance of the payload type */
ItemWrapper&
defaultInit()
{
discard();
build (TY());
return *this;
}
operator bool() const
{
return isValid();
}
/* == value access == */
TY&
operator* () const
{
if (!created_)
throw lumiera::error::State ("accessing uninitialised value/ref wrapper"
, LERR_(BOTTOM_VALUE));
return access();
}
TY*
operator-> () const
{
return & **this;
}
bool
isValid () const
{
return created_;
}
void
reset ()
{
discard();
}
};
/**
* Specialisation of the ItemWrapper to deal with references,
* as if they were pointer values. Allows the reference value
* to be default constructed to ⟂ (invalid) and to be re-assigned.
*/
template<typename TY>
class ItemWrapper<TY &>
{
TY * content_;
public:
ItemWrapper()
: content_()
{ }
explicit
ItemWrapper(TY& o)
: content_( &o )
{ }
// using default copy and assignment
operator bool() const { return isValid(); }
/** allow to re-bind the reference */
ItemWrapper&
operator= (TY& otherRef)
{
content_ = &otherRef;
return *this;
}
ItemWrapper&
defaultInit() ///< @note just reset for this specialisation
{
reset();
return *this;
}
/* == value access == */
TY&
operator* () const
{
if (!content_)
throw lumiera::error::State ("accessing uninitialised reference wrapper"
, LERR_(BOTTOM_VALUE));
return *content_;
}
bool
isValid () const
{
return bool(content_);
}
void
reset ()
{
content_ = 0;
}
};
/**
* Fallback-specialisation for `ItemWrapper<void>`.
* @remark This can be relevant when ItemWrapper is used to capture function results,
* yet the given function has return type `void` and is used for side-effects.
*/
template<>
class ItemWrapper<void>
{
public:
ItemWrapper()
{ }
// using default copy and assignment
operator bool() const { return true; }
bool isValid () const { return true; }
void reset () { /* NOP */ }
/** @warning does nothing */
void operator*() const { /* NOP */ }
ItemWrapper&
defaultInit() ///< no effect for this specialisation
{
return *this;
}
};
/** allow equality comparison if the wrapped types are comparable */
template<typename TY>
inline bool
operator== (ItemWrapper<TY> const& w1, ItemWrapper<TY> const& w2)
{
return (!w1 && !w2)
|| ( w1 && w2 && (*w1)==(*w2));
}
template<typename TY>
inline bool
operator!= (ItemWrapper<TY> const& w1, ItemWrapper<TY> const& w2)
{
return not (w1 == w2);
}
/**
* Extension of ItemWrapper: a function remembering the result of the
* last invocation. Initially, the "value" is bottom (undefined, NIL),
* until the function is invoked for the first time. After that, the
* result of the last invocation can be accessed by `operator* ()`
* @note deliberately non-copyable, since we capture a reference
* to `this` in order to write to the embedded ItemWrapper.
* (to alleviate, we'd have to re-link after copying/moving)
*/
template<typename SIG>
struct FunctionResult
: public function<SIG>
, util::NonCopyable
{
using Res = typename _Fun<SIG>::Ret;
using ResWrapper = ItemWrapper<Res>;
ResWrapper lastResult_;
public:
/** by default locked to _invalid state_ */
FunctionResult() = default;
/**
* Create result-remembering functor by outfitting a _copy_
* of the given function with an adaptor to _capture_ each
* produced result.
* @warning if function result is a value, it is copied.
*/
template<typename FUN>
FunctionResult (FUN&& targetFunction)
: function<SIG>{lib::meta::func::chained
( std::forward<FUN> (targetFunction)
, [this](Res res) -> Res
{
lastResult_ = res;
return std::forward<Res> (res);
})}
{ }
/** retrieve the last function result observed */
Res& operator*() const { return *lastResult_; }
bool isValid () const { return lastResult_.isValid(); }
explicit
operator bool() const { return isValid(); }
};
}} // namespace lib::wrap
#endif