lumiera_/src/lib/wrapper.hpp
Ichthyostega c5679b0fd0 Library: Uninitialised-Storage array (see #1204)
Introduced as remedy for a long standing sloppiness:
Using a `char[]` together with `reinterpret_cast` in storage management helpers
bears danger of placing objects with wrong alignment; moreover, there are increasing
risks that modern code optimisers miss the ''backdoor access'' and might apply too
aggressive rewritings.

With C++17, there is a standard conformant way to express such a usage scheme.
 * `lib::UninitialisedStorage` can now be used in a situation (e.g. as in `ExtentFamily`)
   where a complete block of storage is allocated once and then subsequently used
   to plant objects one by one
 * moreover, I went over the code base and adapted the most relevant usages of
   ''placement-new into buffer'' to also include the `std::launder()` marker
2023-12-02 23:56:46 +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 lumiera::error::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