util::function bugfixes & strong exception guarantee
This commit is contained in:
parent
cb468aa675
commit
c6fe38989c
1 changed files with 60 additions and 17 deletions
|
|
@ -20,6 +20,22 @@ namespace psemek::util
|
||||||
template <typename R, typename ... Args>
|
template <typename R, typename ... Args>
|
||||||
struct function<R(Args...)>
|
struct function<R(Args...)>
|
||||||
{
|
{
|
||||||
|
private:
|
||||||
|
static constexpr std::size_t storage_size = sizeof(void*) * 3;
|
||||||
|
static constexpr std::size_t storage_align = alignof(void*);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct uses_static_storage
|
||||||
|
: std::bool_constant<true
|
||||||
|
&& sizeof(T) <= storage_size
|
||||||
|
&& alignof(T) <= storage_align
|
||||||
|
&& ((storage_align % alignof(T)) == 0)
|
||||||
|
&& std::is_nothrow_move_constructible_v<T>
|
||||||
|
>
|
||||||
|
{};
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
using signature = R(Args...);
|
using signature = R(Args...);
|
||||||
|
|
||||||
function() noexcept = default;
|
function() noexcept = default;
|
||||||
|
|
@ -33,8 +49,18 @@ namespace psemek::util
|
||||||
function (function const &) = delete;
|
function (function const &) = delete;
|
||||||
function & operator = (function const &) = delete;
|
function & operator = (function const &) = delete;
|
||||||
|
|
||||||
|
~function();
|
||||||
|
|
||||||
|
// operator = (F && f) has strong exception guarantree:
|
||||||
|
// if the assignment throws, the function remains unchanged
|
||||||
|
|
||||||
template <typename F>
|
template <typename F>
|
||||||
function & operator = (F && f);
|
std::enable_if_t<uses_static_storage<std::decay_t<F>>::value, function &>
|
||||||
|
operator = (F && f);
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
std::enable_if_t<!uses_static_storage<std::decay_t<F>>::value, function &>
|
||||||
|
operator = (F && f);
|
||||||
|
|
||||||
explicit operator bool() const
|
explicit operator bool() const
|
||||||
{
|
{
|
||||||
|
|
@ -46,10 +72,9 @@ namespace psemek::util
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
private:
|
void swap(function & other) noexcept;
|
||||||
static constexpr std::size_t storage_size = sizeof(void*) * 3;
|
|
||||||
static constexpr std::size_t storage_align = alignof(void*);
|
|
||||||
|
|
||||||
|
private:
|
||||||
std::aligned_storage_t<storage_size, storage_align> storage_;
|
std::aligned_storage_t<storage_size, storage_align> storage_;
|
||||||
|
|
||||||
struct vtable
|
struct vtable
|
||||||
|
|
@ -78,17 +103,28 @@ namespace psemek::util
|
||||||
|
|
||||||
template <typename R, typename ... Args>
|
template <typename R, typename ... Args>
|
||||||
template <typename F>
|
template <typename F>
|
||||||
function<R(Args...)> & function<R(Args...)>::operator = (F && f)
|
std::enable_if_t<function<R(Args...)>::template uses_static_storage<std::decay_t<F>>::value, function<R(Args...)> &>
|
||||||
|
function<R(Args...)>::operator = (F && f)
|
||||||
{
|
{
|
||||||
reset();
|
reset();
|
||||||
assign(std::forward<F>(f));
|
assign(std::forward<F>(f));
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename R, typename ... Args>
|
||||||
|
template <typename F>
|
||||||
|
std::enable_if_t<!function<R(Args...)>::template uses_static_storage<std::decay_t<F>>::value, function<R(Args...)> &>
|
||||||
|
function<R(Args...)>::operator = (F && f)
|
||||||
|
{
|
||||||
|
function(std::forward<F>(f)).swap(*this);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename R, typename ... Args>
|
template <typename R, typename ... Args>
|
||||||
function<R(Args...)>::function (function && other) noexcept
|
function<R(Args...)>::function (function && other) noexcept
|
||||||
{
|
{
|
||||||
vtable_ = other.vtable_;
|
vtable_ = other.vtable_;
|
||||||
|
if (vtable_)
|
||||||
vtable_->move(&other.storage_, &storage_);
|
vtable_->move(&other.storage_, &storage_);
|
||||||
other.reset();
|
other.reset();
|
||||||
}
|
}
|
||||||
|
|
@ -99,12 +135,20 @@ namespace psemek::util
|
||||||
if (this == &other)
|
if (this == &other)
|
||||||
return *this;
|
return *this;
|
||||||
|
|
||||||
|
reset();
|
||||||
vtable_ = other.vtable_;
|
vtable_ = other.vtable_;
|
||||||
|
if (vtable_)
|
||||||
vtable_->move(&other.storage_, &storage_);
|
vtable_->move(&other.storage_, &storage_);
|
||||||
other.reset();
|
other.reset();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename R, typename ... Args>
|
||||||
|
function<R(Args...)>::~function()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
template <typename R, typename ... Args>
|
template <typename R, typename ... Args>
|
||||||
template <typename ... Args1>
|
template <typename ... Args1>
|
||||||
R function<R(Args...)>::operator()(Args1 && ... args) const
|
R function<R(Args...)>::operator()(Args1 && ... args) const
|
||||||
|
|
@ -124,27 +168,26 @@ namespace psemek::util
|
||||||
vtable_ = nullptr;
|
vtable_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename R, typename ... Args>
|
||||||
|
void function<R(Args...)>::swap(function & other) noexcept
|
||||||
|
{
|
||||||
|
std::swap(*this, other);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename R, typename ... Args>
|
template <typename R, typename ... Args>
|
||||||
template <typename F>
|
template <typename F>
|
||||||
void function<R(Args...)>::assign(F && f)
|
void function<R(Args...)>::assign(F && f)
|
||||||
{
|
{
|
||||||
using T = std::decay_t<F>;
|
using T = std::decay_t<F>;
|
||||||
|
|
||||||
constexpr bool static_storage = true
|
if constexpr (uses_static_storage<T>::value)
|
||||||
&& sizeof(T) <= storage_size
|
|
||||||
&& alignof(T) <= storage_align
|
|
||||||
&& ((storage_align % alignof(T)) == 0)
|
|
||||||
&& std::is_nothrow_move_constructible_v<T>
|
|
||||||
;
|
|
||||||
|
|
||||||
if constexpr (static_storage)
|
|
||||||
{
|
{
|
||||||
new (reinterpret_cast<T *>(&storage_)) T(std::move(f));
|
new (reinterpret_cast<T *>(&storage_)) T(std::move(f));
|
||||||
|
|
||||||
static vtable m = {
|
static vtable m = {
|
||||||
[](void * src, void * dst){ new (reinterpret_cast<T*>(dst)) T(std::move(*reinterpret_cast<T*>(src))); },
|
[](void * src, void * dst){ new (reinterpret_cast<T*>(dst)) T(std::move(*reinterpret_cast<T*>(src))); },
|
||||||
[](void * src){ reinterpret_cast<T*>(src)->~T(); },
|
[](void * src){ reinterpret_cast<T*>(src)->~T(); },
|
||||||
[](void * src, Args ... args) -> R { return (*reinterpret_cast<T*>(src))(static_cast<Args&&>(args)...); }
|
[](void * src, Args ... args) -> R { return std::invoke(*reinterpret_cast<T*>(src), static_cast<Args&&>(args)...); }
|
||||||
};
|
};
|
||||||
|
|
||||||
vtable_ = &m;
|
vtable_ = &m;
|
||||||
|
|
@ -156,7 +199,7 @@ namespace psemek::util
|
||||||
static vtable m = {
|
static vtable m = {
|
||||||
[](void * src, void * dst){ *reinterpret_cast<T**>(dst) = *reinterpret_cast<T**>(src); *reinterpret_cast<T**>(src) = nullptr; },
|
[](void * src, void * dst){ *reinterpret_cast<T**>(dst) = *reinterpret_cast<T**>(src); *reinterpret_cast<T**>(src) = nullptr; },
|
||||||
[](void * src){ delete *reinterpret_cast<T**>(src); },
|
[](void * src){ delete *reinterpret_cast<T**>(src); },
|
||||||
[](void * src, Args ... args) -> R { return (**reinterpret_cast<T**>(src))(static_cast<Args&&>(args)...); }
|
[](void * src, Args ... args) -> R { return std::invoke(**reinterpret_cast<T**>(src), static_cast<Args&&>(args)...); }
|
||||||
};
|
};
|
||||||
|
|
||||||
vtable_ = &m;
|
vtable_ = &m;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue