util::function bugfixes & strong exception guarantee

This commit is contained in:
Nikita Lisitsa 2020-11-23 17:56:35 +03:00
parent cb468aa675
commit c6fe38989c

View file

@ -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,18 +103,29 @@ 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_;
vtable_->move(&other.storage_, &storage_); if (vtable_)
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_;
vtable_->move(&other.storage_, &storage_); if (vtable_)
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;